Compare commits

..

No commits in common. "master" and "v1.0.0-beta.1" have entirely different histories.

778 changed files with 163164 additions and 115680 deletions

View File

@ -1,55 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
assignees: ''
---
<!--
IMPORTANT!!! IMPORTANT!!! IMPORTANT!!! IMPORTANT!!!
YOU SHOULD NEVER DELETE THE CONTENT OF THIS TEMPLATE WHEN OPENING AN ISSUE. IF YOUR QUESTION DOES NOT FIT THE TEMPLATE THEN IT MOST PROBABLY BELONGS TO OPENVIDU FORUM (https://openvidu.discourse.group/)
Hi! First of all, welcome to OpenVidu issue tracker. Please, carefully read the two points below before opening a new issue:
1. Is your question really a bug? In other words: did you actually get an unexpected behavior from OpenVidu platform? If you are not sure about the answer or you just want support for a particular use case, you can post a new question in our official Discourse Forum (https://openvidu.discourse.group/). OpenVidu community or a team's member will reply ASAP.
2. If your question is undoubtedly a bug, check that there's no other issue (opened or closed) talking about it. Your question may have already been answered! If you cannot find anything useful, please fill the report below.
-->
**Describe the bug**
A few words describing what the bug is.
**Expected behavior**
A clear and concise description of what you expected to happen.
**Wrong current behavior**
A clear and concise description of what is actually happening instead of the expected behavior.
**OpenVidu tutorial where to replicate the error**
This is an EXTREMELY IMPORTANT STEP. If we are able to replicate the error in any of the official [OpenVidu Tutorials](https://github.com/OpenVidu/openvidu-tutorials) or [OpenVidu Demos](https://github.com/OpenVidu), then we will be able to quickly fix it. If you are getting the error in your own application, please try to add the necessary changes to the most similar tutorial so it fails with the same error (try to keep those changes as contained as possible, so that the original tutorial maintains its integrity). Once you have an application to replicate the error, explain in detail the steps to get it like this:
1. Clone repository [...]
2. Compile the application like this [...]
3. Run OpenVidu Server like this [...]
4. Run the application like this [...]
5. Join 1 user... Publish a video stream [...]
6. See error
**OpenVidu deployment info**
How is your OpenVidu Server instance deployed when you get the bug. A couple of possible examples are listed below:
- Docker container as explained in OpenVidu tutorials, run with command `docker run ...` on macOS Catalina 10.15.1
- AWS deployment as explained in OpenVidu Docs (https://docs.openvidu.io/en/stable/deployment/deploying-aws/)
> **IMPORTANT NOTE**: please, if you think the bug might be related to OpenVidu Server side, specify here if you are also getting the error by using OpenVidu Server Demos instance. This instance is publicly available (use it only for this test, because it is not secure!!!): **URL**: `https://demos.openvidu.io:4443`, **SECRET**: `MY_SECRET`
**Client device info (if applicable)**
Describe the client device(s) or platform(s) where you are able to replicate the error. For example:
- Chrome 78.0.3904.97 (Official Build) (64-bit) on Windows 10 (1903).
- Firefox Mobile 68.2.0 running on OnePlus 6 with Android 9.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here. For example, attach any useful logs related to the issue.

View File

@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: OpenVidu Discourse forum
url: https://openvidu.discourse.group/
about: If your question is about a certain feature or getting support for a specific use case, please use OpenVidu Discourse Group forum

View File

@ -1,478 +0,0 @@
name: openvidu-components-angular Tests
on:
push:
branches:
- master
paths:
- 'openvidu-components-angular/**'
- '.github/workflows/openvidu-components-angular-tests.yml'
pull_request:
branches:
- master
workflow_dispatch:
inputs:
commit_sha:
description: 'Commit SHA'
required: false
default: ''
jobs:
test_setup:
name: Test setup
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Commit URL
run: echo https://github.com/OpenVidu/openvidu/commit/${{ inputs.commit_sha || github.sha }}
- name: Send Dispatch Event
env:
GITHUB_TOKEN: ${{ secrets.OPENVIDU_DISPATCH_EVENT_GA }}
COMMIT_MESSAGE: ${{ github.event.head_commit.message || 'Manually' }}
COMMIT_URL: ${{ github.event.commits[0].url || 'Manually' }}
BRANCH_NAME: ${{ github.ref_name }}
run: |
curl -X POST \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'", "branch-name":"'"$BRANCH_NAME"'"}}'
nested_events:
needs: test_setup
name: Nested events
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Install dependencies
run: |
cd openvidu-components-angular
npm install
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested components E2E event tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-events --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
nested_structural_directives:
needs: test_setup
name: Nested Structural Directives
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested structural directives tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-structural-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
nested_attribute_directives:
needs: test_setup
name: Nested Attribute Directives
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run nested attribute directives tests
env:
LAUNCH_MODE: CI
run: npm run e2e:nested-attribute-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_directives:
needs: test_setup
name: API Directives Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_internal_directives:
needs: test_setup
name: Internal Directives Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-internal-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_chat:
needs: test_setup
name: Chat E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-chat --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_events:
needs: test_setup
name: Events E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-events --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_media_devices:
needs: test_setup
name: Media devices E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-media-devices --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_panels:
needs: test_setup
name: Panels E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-panels --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_screen_sharing:
needs: test_setup
name: Screen sharing E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-screensharing --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_stream:
needs: test_setup
name: Stream E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -v $(pwd)/openvidu-components-angular/e2e/assets:/e2e-assets selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-stream --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_toolbar:
needs: test_setup
name: Toolbar E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Webcomponent E2E
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-toolbar --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_virtual_backgrounds:
needs: test_setup
name: Virtual Backgrounds E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Webcomponent E2E
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-virtual-backgrounds --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main

View File

@ -1,56 +0,0 @@
name: OpenVidu integration tests
on:
push:
branches:
- master
paths:
- "openvidu-test-integration/**"
- ".github/workflows/openvidu-integration-tests.yml"
workflow_dispatch:
jobs:
integration-tests:
name: Integration tests
timeout-minutes: 30
runs-on: ubuntu-latest
steps:
- name: Configure OpenVidu Local Deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
with:
ref-openvidu-local-deployment: development
pre_startup_commands: |
sed -i 's/interval: 10s/interval: 1s/' livekit.yaml
sed -i '/interval: 1s/a \ fixer_interval: 10s' livekit.yaml
- name: Install LiveKit CLI
run: |
curl -sSL https://get.livekit.io/cli | bash
- name: Checkout current repository
uses: actions/checkout@v4
with:
path: openvidu
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 22
- name: Install dependencies
working-directory: ./openvidu/openvidu-test-integration
run: npm ci
- name: Run tests
working-directory: ./openvidu/openvidu-test-integration
run: npm run test:ci
- name: Upload report
uses: actions/upload-artifact@v4
if: always()
with:
name: openvidu-integration-tests-report
path: ./openvidu/openvidu-test-integration/test-results.json
retention-days: 7
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main

12
.gitignore vendored
View File

@ -2,12 +2,9 @@
*/target/*
!openvidu-java-client/target/openvidu-java-client.jar
/target
.classpath
.idea
.project
.settings
*.iml
*orig
.springBeans
*tmp/
@ -18,12 +15,3 @@ nbactions.xml
*bower_components/
.externalToolBuilders
*bin/
*/.vscode/*
.vscode/*
*/.sts4-cache/*
*/.project
*/.classpath
*/.settings/*
*/.tscache/*
.factorypath

3
.gitmodules vendored
View File

@ -1,3 +0,0 @@
[submodule "openvidu-livekit"]
path = openvidu-livekit
url = https://github.com/OpenVidu/openvidu-livekit.git

View File

@ -1,67 +0,0 @@
# Contribute
## Introduction
First, thank you for considering contributing to openvidu! It's people like you that make the open source community such a great community! 😊
We welcome any type of contribution, not only code. You can help with
- **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
- **Marketing**: writing blog posts, howto's, printing stickers, ...
- **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
- **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
- **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/openvidu).
## Your First Contribution
Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
## Submitting code
Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
## Code review process
The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
## Financial contributions
We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/openvidu).
Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
## Questions
If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
You can also reach us at hello@openvidu.opencollective.com.
## Credits
### Contributors
Thank you to all the people who have already contributed to openvidu!
<a href="graphs/contributors"><img src="https://opencollective.com/openvidu/contributors.svg?width=890" /></a>
### Backers
Thank you to all our backers! [[Become a backer](https://opencollective.com/openvidu#backer)]
<a href="https://opencollective.com/openvidu#backers" target="_blank"><img src="https://opencollective.com/openvidu/backers.svg?width=890"></a>
### Sponsors
Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/openvidu#sponsor))
<a href="https://opencollective.com/openvidu/sponsor/0/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/1/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/2/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/3/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/4/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/5/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/6/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/7/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/8/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/9/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/9/avatar.svg"></a>
<!-- This `CONTRIBUTING.md` is based on @nayafia's template https://github.com/nayafia/contributing-template -->

3
NOTICE
View File

@ -1,4 +1,4 @@
(C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
(C) Copyright 2016 Kurento (http://kurento.org)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -11,4 +11,3 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

690
README.md
View File

@ -1,59 +1,659 @@
[![Backers on Open Collective](https://opencollective.com/openvidu/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/openvidu/sponsors/badge.svg)](#sponsors)
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![OpenVidu Tests](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml/badge.svg)](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml)
[![Npm version](https://img.shields.io/npm/v/openvidu-browser?label=npm-version)](https://npmjs.org/package/openvidu-browser)
[![Npm downloads](https://img.shields.io/npm/dw/openvidu-browser?label=npm2-downloads)](https://npmjs.org/package/openvidu-browser)
What is OpenVidu?
========
OpenVidu is a platform to facilitate the addition of video calls in your web or mobile
application, either group or one-to-one calls. In fact, any combination you come up with is easy to implement with OpenVidu.
It is based on [Kurento](http://www.kurento.org), the WebRTC platform for multimedia applications. Openvidu was forked from [KurentoRoom project](https://github.com/Kurento/kurento-room).
OpenVidu and Kurento are licensed under Apache License v2.
----------
[![Documentation Status](https://readthedocs.org/projects/openvidu/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/openvidu/openvidu-server-kms.svg)](https://hub.docker.com/r/openvidu/openvidu-server-kms)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![Twitter Follow](https://img.shields.io/twitter/follow/openvidu.svg?style=social)](https://twitter.com/openvidu)
Table of contents
========
[![][OpenViduLogo]](https://openvidu.io)
* [Running a videocall demo](#running-a-videocall-demo-application)
* [Building a simple app](#building-a-simple-app-with-openvidu)
* [Securization](#securization)
* [Sharing data between users](#sharing-data-between-users)
* [API Reference](#api-reference)
* [Deploying on AWS](#deploying-on-aws)
* [Developing OpenVidu](#developing-openvidu)
* [Acknowledgments](#acknowledgments)
openvidu
===
Visit [openvidu.io](https://openvidu.io)
## Community Forum
Visit [OpenVidu Community Forum](https://openvidu.discourse.group/)
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
## Contributors
This project exists thanks to all the people who contribute.
<a href="https://github.com/OpenVidu/openvidu/contributors"><img src="https://opencollective.com/openvidu/contributors.svg?width=890&button=false" /></a>
----------
## Backers
Running a videocall demo application
====================================
We have implemented a very basic demo application to see OpenVidu in action. To ease the installation, we have packaged it as a docker image.
Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/openvidu#backer)]
- Please be sure that you have [docker CE installed](https://store.docker.com/search?type=edition&offering=community)
- Run this Docker container
```
docker run -p 5000:5000 -p 4040:4040 -e KMS_STUN_IP=193.147.51.12 -e KMS_STUN_PORT=3478 -e openvidu.security=false openvidu/openvidu-plainjs-demo
```
- Wait until you see a public URL ended with `.ngrok.io`. You can connect locally in [`localhost:5000`](http://localhost:5000) or by using the ngrok public URL. You can also share this URL with anyone you want to test the app over the Internet!
----------
<a href="https://opencollective.com/openvidu#backers" target="_blank"><img src="https://opencollective.com/openvidu/backers.svg?width=890"></a>
Building a simple app with OpenVidu
===================
## Acknowledgments
<p align="center">
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSNF9ZWHREUXo3QlE">
</p>
OpenVidu has been supported under project "CPP2021-008720 NewGenVidu: An elastic, user-friendly and privacy-friendly videoconferencing platform", funded by MCIN/AEI/10.13039/501100011033 and by the European Union-NextGenerationEU/PRTR.
OpenVidu has a traditional **Client - Server** architecture built on three modules that are shown in the image above. To run **openvidu-server** and **Kurento Media Server** you can execute the following container:
<img height="75px" src="https://docs.openvidu.io/en/stable/img/logos/support.jpg">
```
docker run -p 8443:8443 --rm -e KMS_STUN_IP=193.147.51.12 -e KMS_STUN_PORT=3478 -e openvidu.security=false openvidu/openvidu-server-kms
```
Then, you have to use the library **openvidu-browser** in your JavaScript browser application (frontend). This library is packaged in [OpenVidu.js] file that you can download from https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/main/resources/static/js/OpenVidu.js. Then add the file in your HTML with `<script src="OpenVidu.js"></script>`.
With the **openvidu-browser** library you can handle all available operations straight away from your client, as creating video calls, joining users to them or publishing/unpublishing video and audio
## Sample application
Once you have up and running Kurento Media Server and openvidu-server, you just need to add a few lines of code in your frontend to make your first video call with OpenVidu. You can take a look to the simplest sample application in GitHub https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-insecure-js.
You can clone the repo and serve the app locally with your favourite tool (we recommend http-server: `npm install -g http-server`)
```
git clone https://github.com/OpenVidu/openvidu-tutorials.git
cd openvidu-tutorials/openvidu-insecure-js/web
http-server
```
You can now start editing HTML, JS and CSS files. Just reload your browser to see your changes (mind the browser's cache!).
### Code description
1. Get an *OpenVidu* object and initialize a session with a *sessionId*. Have in mind that this is the parameter that defines which video call to connect.
```javascript
var OV = new OpenVidu("wss://" + OPENVIDU_SERVER_IP + ":8443/");
var session = OV.initSession(sessionId);
```
2. Set the events to be listened by your session. For example, this snippet below will automatically append the new participants videos to HTML element with 'subscriber' id. Available events for the Session object are detailed in [API section](#session).
```javascript
session.on('streamCreated', function (event) {
session.subscribe(event.stream, 'subscriber');
});
```
3. Connect to the session. For a non-secure approach, the value of *token* parameter is irrelevant. You can pass as second parameter a callback to be executed after connection is stablished. A common use-case for users that want to stream their own video is the following one: if the connection to the session has been succesful, get a Publisher object (appended to HTML element with id 'publisher') and publish it. The rest of participants will receive the stream.
```javascript
session.connect(token, function (error) {
// If connection successful, get a publisher and publish to the session
if (!error) {
var publisher = OV.initPublisher('publisher', {
audio: true,
video: true,
quality: 'MEDIUM' //'LOW','MEDIUM','HIGH'
});
session.publish(publisher);
} else {
console.log('Error while connecting to the session');
}
});
```
4. Finally, whenever you want to leave the video call...
```javascript
session.disconnect();
```
With these few lines of code you will already have a functional video-call capability in your app. Check [Securization](#securization) section to learn how to easily make your app ready for production.
If you prefer, there's an Angular version of the sample app that uses _openvidu-browser_ npm package. Check it out [here](https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-insecure-angular).
----------
Securization
===================
## Why?
In a production environment probably you don't want unauthorized users swamping your video calls. It's not possible to control access to them with the first approach we have seen in the sections above: anyone who knows the _sessionId_ could connect to your video call, and if it turns out that the _sessionId_ doesn't belong to an existing session, a new one would be created.
In addition, a secure version also means you can choose the role each user has in your video calls (see [OpenViduRole](#openvidurole) section).
Thus, a non-secure version of OpenVidu is only intended for development environments. Don't worry, adding securization is not a difficult task.
## How?
<p align="center">
<img src="https://docs.google.com/uc?id=0B61cQ4sbhmWSeDNIekd5R2ZhQUE">
</p>
In the image above you can see the main difference with the non-secure version of OpenVidu. Your backend will now have to call two HTTP REST operations in openvidu-server to get the two parameters needed in the securization process:
- ***sessionId***: just as in the non-secure version, it identifies each specific video-call
- ***token***: any user joining a specific video call will need to pass a valid token as a parameter
For the moment you have two options available for getting sessionIds and tokens from openvidu-server:
#### REST API
As stated in the former point, two REST operations are provided: ***/getSessionId*** and ***/newToken***.
Both operations have in common the header referred to authorization. It is implemented via Basic Auth, and it is as simple as applying Base64 encoding to the username (always "OPENVIDUAPP") and the password (your **secret** shared with openvidu-server). An example is shown below:
For secret "MY_SECRET", the final header would be
> Authorization:Basic T1BFTlZJRFVBUFA6TVlfU0VDUkVU
| _NEW SESSIONID_ | _PARAMETERS_ |
| --------- | -- |
| **Operation** | POST |
| **URL** | https://[YOUR_OPENVIDUSERVER_IP]/api/sessions |
| **Headers** | Authorization:Basic _EncodeBase64(OPENVIDUAPP:[YOUR_SECRET])_ |
| **Returns** | {"id": "SESSIONID"} |
| _NEW TOKEN_ | _PARAMETERS_ |
| --------- | -- |
| **Operation** | POST |
| **URL** | https://[YOUR_OPENVIDUSERVER_IP]/api/tokens |
| **Headers** | Authorization:Basic _EncodeBase64(OPENVIDUAPP:[YOUR_SECRET])_<br/>Content-Type:application/json |
| **Body** | {"session": "SESSIONID", "role": "ROLE", "data": "DATA"} |
| **Returns** | {"token": "TOKEN", "session": "SESSIONID", "role": "ROLE", "data": "DATA", "id": "TOKEN"} |
> **ROLE** value in Body field of POST to "/api/tokens" can be:
>
> - SUBSCRIBER
> - PUBLISHER
> - MODERATOR
>
> (See [OpenViduRole](#openvidurole) section)
#### openvidu-backend-client
A Java package that wraps the HTTP REST operations for making them even easier
- Maven dependency
```xml
<dependency>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-java-client</artifactId>
<version>...</version>
</dependency>
```
- Jar
[```https://github.com/OpenVidu/openvidu/tree/master/openvidu-java-client/target/openvidu-java-client.jar```](https://github.com/OpenVidu/openvidu/tree/master/openvidu-java-client/target/openvidu-java-client.jar)
The usage is quite simple: import OpenVidu package and get an **OpenVidu** object. You need to provide to the constructor the IP of your OpenVidu Server and the secret shared with it (initialized by `openvidu.secret=MY_SECRET` property). Then just call the following methods to get a shiny new sessionId or token to be returned to your frontend.
```java
import io.openvidu.java.client.OpenVidu;
OpenVidu openVidu = new OpenVidu(OPENVIDU_SERVER_IP, YOUR_SECRET);
Session session = this.openVidu.createSession();
String sessionId = session.getSessionId();
String token = session.generateToken();
// Send sessionId and token to frontend
```
## A sequence diagram to sum up
<p align="center">
<img src="http://www.plantuml.com/plantuml/png/ZP9TIyCm58QlcrznY3SJNFsus8KNmgPJAcKPRWWYkyYQT8HXKfBiu-URkcn4cxhUji_xdFSSOjP2LbJJBnYf_PGo9kGARcyGSX-jA4H5fGNyeJOQdhMI5l_-eIekju9j-akjTePhZ11QgZtW7vXBXk623ygxiaH9gp4vetGQSD9Ofn4jrcsLN7ORDAhHKw6I3sA53hhaVz-fJhW4z1z21zp3PqvUiWcGwVXjEC_8P76EVoKEVy-UnWGUXtc-G6cQ7eSSe3hpjuvBNg-udN5ZX98PGqsYCGgR8uqx-INVpPKxNYUthSccDzpTBHjKkFAHo84QRy55NHdms_O2ooMAqCt1Fj1jb8VJGad92zlpHLj7nMxdizy0">
</p>
1. Identify your user and listen to a request for joining a video call (represented by [LOGIN OPERATION] and [JOIN VIDEO CALL] in the diagram). This process is entirely up to you.
2. You must get a _sessionId_: a new one if the video call is being created or an existing one for an active video call. In the first case you need to ask openvidu-server for it (as shown in the diagram), in the second case you must retrieve it from wherever you stored it when it was created (a data-base or maybe your backend itself).
3. You also need a new valid _token_ for this session. Ask openvidu-server for it passing the _sessionId_.
4. Finally return both parameters to your frontend, where using openvidu-browser you may initilize your session with _sessionId_ and then connect to it with _token_. Good news: **the code is exactly the same as explained before in [Code description](#code-description) section**
> Communication between _Your Back_ and _openvidu-server_ modules is outlined in the diagram, but it does not correspond to the real methods. Remember you can handle this from your backend by consuming the [REST API](#rest-api) or by using [openvidu-backend-client](#openvidu-backend-client) package.
## Running a secure videocall application
We have implemented a very basic [demo application](https://github.com/OpenVidu/openvidu-tutorials/tree/master/openvidu-js-java) to see the secure version of OpenVidu in action. It has a Java backend to manage the user sessions and the securization process with OpenVidu Server.
- Please be sure that you have [docker CE installed](https://store.docker.com/search?type=edition&offering=community)
- Run this Docker container
```
docker run -p 5000:5000 -p 3000:3000 -p 4040:4040 -e KMS_STUN_IP=193.147.51.12 -e KMS_STUN_PORT=3478 openvidu/openvidu-sample-secure
```
- Wait until you see a public URL ended with `.ngrok.io`. You can connect locally in [`localhost:3000`](http://localhost:3000) or by using the ngrok public URL. You can also share this URL with anyone you want to test the app over the Internet!
## Running a sample advanced app
Wanna try a [real sample application](https://github.com/OpenVidu/openvidu/tree/master/openvidu-sample-app) that makes use of everything we have talked about? Take a look at this app. It wraps a frontend built with Angular, a backend built with Spring and a MySQL database:
- Please be sure that you have docker-compose (`sudo apt-get install docker-compose`)
- Download the `docker-compose.yml` file and run it:
```
wget -O docker-compose.yml https://raw.githubusercontent.com/OpenVidu/openvidu-docker/master/openvidu-sample-app/docker-compose.yml
docker-compose up
```
- Wait until you see an output like `Started App in XXX seconds (JVM running for XXX)`
- Go to [`https://localhost:5000`](https://localhost:5000) and accept the self-signed certificate. Here you have a couple registered users (use a standard window and an incognito window to test both of them at the same time):
| user | password |
| -------------------------- | ----------- |
| teacher@<span></span>gmail.com | pass |
| student1@<span></span>gmail.com | pass |
----------
Sharing data between users
===================
Whatever app you are developing, chances are you will need to pass some data for each user, at least a nickname. You can do it in two different places:
- **openvidu-browser**: when calling `session.connect` method
```
session.connect(token, DATA, function (error) { ... });
```
- **API REST**: when asking for a token to */api/tokens*, you can pass data as third parameter in the BODY of the POST request
```
{“session”: “sessionId”, “role”: “role”, “data”: "DATA"}
```
> Java and Node clients (_openvidu-java-client_ and _openvidu-node-client_) allow you to pass data when creating a Token object: </br>
> `tokenOptions = new TokenOptions.Builder().data("DATA").build();`
The result will be that in all clients, *Connection* objects will have in their *data* property the pertinent value you have provided for each user. So, an easy way to get the data associated to any user would be:
```javascript
session.on('streamCreated', function (event) {
session.subscribe(event.stream, 'subscriber');
console.log('USER DATA: ' + event.stream.connection.data);
});
```
Some clarifications:
- *Connection.data* will be a simple string if you have provided data only with one of the methods, and will be a string with the following format if you provide data both from openvidu-browser and your backend: "OPENVIDUBROWSER_DATA%/%APIREST_DATA"
- Using only first option is not secure, as clients could modify the value of the second parameter. It is intended only in development environments. If you want total control over shared data, please use the second way.
- You can choose whatever format you like for the data string, but if you are planning to share more than a simple field, maybe a standard format as JSON would be a wise choice.
----------
API reference
===================
> NOTE: all input parameters ("Parameters" columns) are listed in strict order, optional ones in _italics_
## openvidu-browser
| Class | Description |
| ---------- | ---------------------------------------------------------- |
| OpenVidu | Use it to initialize your sessions and publishers |
| Session | Represents a video call. It can also be seen as a room where multiple users can connect. Participants who publish their videos to a session will be seen by the rest of users connected to that specific session |
| Publisher | Packs local media streams. Users can publish it to a session |
| Subscriber | Packs remote media streams. Users automatically receive them when others publish their streams|
| Stream | Represents each one of the videos send and receive by a user in a session. Therefore each Publisher and Subscriber has an attribute of type Stream |
| Connection | Represents each one of the user's connection to the session (the local one and other user's connections). Therefore each Session and Stream object has an attribute of type Connection |
#### **OpenVidu**
| Method | Returns | Parameters | Description |
| ---------------- | ------- | ------------------------------------------- | ----------- |
| `initSession` | Session | _`apikey:string`_<br/>`sessionId:string` | Returns a session with id **sessionId** |
| `initPublisher` | Publisher | `parentId:string`<br/>_`cameraOptions:any`_<br/>_`callback:function`_ | Starts local video stream, appending it to **parentId** HTML element, with the specific **cameraOptions** settings and executing **callback** function in the end. _cameraOptions_ must be an object with three properties: **{audio:boolean, video:boolean, quality:string}**, being _audio_/_video_ false if you want to initialize them muted (_Publisher.publishAudio(true)_ and _Publisher.publishVideo(true)_ can unmute them later) and _quality_ must be 'LOW', 'MEDIUM' or 'HIGH'|
| `checkSystemRequirements` | Number | | Returns 1 if the browser supports WebRTC, 0 otherwise|
| `getDevices` | Promise | `callback(error, deviceInfo):function` | Collects information about the media input and output devices available on the system, returned in **deviceInfo** array |
#### **Session**
| Method | Returns | Parameters | Description |
| ---------------- | ------- | ------------------------------------------- | ----------- |
| `connect` | | `token:string`<br/>_`metadata:string`_<br/>`callback(error):function` | Connects to the session using **token** and executes **callback** in the end (_error_ parameter null if success). **metadata** parameter allows you to pass a string as extra data to share with other users when they receive _participantJoined_ event. You can also add metadata through openvidu-backend-client when generating tokens (see [TokenOptions](#tokenoptions)). The structure of this string is up to you (maybe some standarized format as JSON or XML is a good idea), the only restriction is a maximum length of 1000 chars |
| `disconnect` | | | Leaves the session, destroying all streams and deleting the user as a participant |
| `publish` | | `publisher:Publisher` | Publishes the specific user's local stream contained in **publisher** object to the session |
| `unpublish` | | `publisher:Publisher` | Unpublishes the specific user's local stream contained in **publisher** object |
| `on` | | `eventName:string`<br/>`callback:function` | **callback** function will be triggered each time **eventName** event is recieved |
| `once` | | `eventName:string`<br/>`callback:function` | **callback** function will be triggered once when **eventName** event is recieved. The listener is removed immediately |
| `off` | | `eventName:string`<br/>`eventHandler:any` | Removes **eventHandler** handler for **eventName** event |
| `subscribe` | Subscriber | `stream:Stream`<br/>`htmlId:string`<br/>_`videoOptions:any`_ | Subscribes to **stream**, appending a new HTML Video element to DOM element of **htmlId** id, with **videoOptions** settings. This method is usually called in the callback of _streamCreated_ event |
| `unsubscribe` | | `subscriber:Subscriber` | Unsubscribes from **subscriber**, automatically removing its HTML Video element |
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
| `sessionId` | string | The unique id of the session |
| Event | Properties | Description |
| -----------------------| --------------------- | ---------------------------- |
| `streamCreated` | stream:Stream | Triggered by Session object when a new Stream has been created and added to it |
| `streamDestroyed` | stream:Stream<br/>preventDefault():Function | Triggered by Session object when an existing Stream has been destroyed. The default behaviour is the deletion of the HTML video element associated to it. To prevent it, call `preventDefault()` method on the event object |
| `connectionCreated` | connection:Connection | Triggered by Session object whenever any user has joined the session. This includes dispatching one event for each user that joins the session when you are already connected to it, one for each existing participant the first time you connect to the session and once for your own local connection |
| `connectionDestroyed` | connection:Connection | Triggered by Session object whenever a user leaves the session. This event can also mean that `streamDestroyed` events could be dispatched, depending on the streams associated to it |
| `sessionDisconnected` | preventDefault():Function | Triggered by Session object when the user disconnects from the Session. Default behaviour is the deletion of all HTML video elements. Call `preventDefault()` on event object to prevent it and delete them by yourself |
#### **Publisher**
| Method | Returns | Parameters | Description |
| -------------- | ------- | ------------------------------------------- | ----------- |
| `publishAudio` | | `value:boolean`| Enable or disable the audio track depending on whether value is _true_ or _false_ |
| `publishVideo` | | `value:boolean`| Enable or disable the video track depending on whether value is _true_ or _false_ |
| `destroy` | Publisher || Delets the publisher object and removes it from DOM. The rest of users will trigger a _streamDestroyed_ event |
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
| `accessAllowed` | boolean | _true_ if the user has granted access to the camera, _false_ otherwise |
| `element` | Element | The parent HTML Element which contains the publisher |
| `id` | string | The id of the HTML Video element of the publisher |
| `stream` | Stream | The stream object of the publisher |
| `session` | Session | The session to which the publisher belongs |
| Event | Properties | Description |
| -----------------------| --------------------- | ---------------------------- |
| `accessAllowed` | | Triggered by Publisher object when the user has granted access to the camera/microphone |
| `accessDenied` | | Triggered by Publisher object when the user has rejected access to the camera/microphone |
| `videoElementCreated` | element:HTMLVideoElement | Triggered by Publisher object inmediately after a new video element has been added to DOM |
#### **Subscriber**
| Method | Returns | Parameters | Description |
| -------------- | ------- | ------------------------------------------- | ----------- |
| | | | |
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
| `element` | Element | The parent HTML Element which contains the subscriber |
| `id` | string | The id of the HTML Video element of the subscriber |
| `stream` | Stream | The stream object of the subscriber |
| Event | Properties | Description |
| -----------------------| --------------------- | ---------------------------- |
| `videoElementCreated` | element:HTMLVideoElement | Triggered by Subscriber object inmediately after a new video element has been added to DOM |
#### **Connection**
| Property | Type | Description |
| ------------| ------ | ---------------------------- |
| `connectionId` | string | Unique identifier of the connection |
| `data` | string | Data associated to this connection (and therefore to certain user). This is an important field: it allows you to broadcast all the information you want for each user (a username, for example) |
| `creationTime` | number | Time when this connection was created |
## openvidu-backend-client
| Class | Description |
| ------------ | ------------------------------------------------------- |
| OpenVidu | Use it to create all the sessions you need |
| Session | Allows for the creation of tokens |
| OpenViduRole | Enum that defines the values accepted by _TokenOptions.Builder.role(OpenViduRole role)_ method |
| TokenOptions | Customize each token with this class when generating them |
#### **OpenVidu**
| Method | Returns | Parameters | Description |
| -------------- | ------- | --------------------------------------------- | ----------- |
| OpenVidu() | | `String:urlOpenViduServer`<br>`String:secret` | The constructor receives the URL of your OpenVidu Server and the secret shared with it |
| createSession() | Session | | Get a Session object by calling this method. You can then store it as you want |
#### **Session**
| Method | Returns | Parameters | Description |
| -------------- | ------- | --------------------------------------------- | ----------- |
| getSessionId() | String | | Returns the unique identifier of the session. You will need to return this parameter to the client side to pass it during the connection process to the session |
| generateToken() | String | _`TokenOptions:tokenOptions`_ | The value returned is required in the client side just as the sessionId in order to connect to a session |
#### **OpenViduRole**
| Enum | Description |
| ---------- | ------- |
| SUBSCRIBER | They can subscribe to published streams of other users |
| PUBLISHER | They can subscribe to published streams of other users and publish their own streams|
| MODERATOR | They can subscribe to published streams of other users, publish their own streams and force _unpublish()_ and _disconnect()_ over a third-party stream or user |
#### **TokenOptions**
| Method | Returns | Parameters | Description |
| -------------- | ------- | -------------------------------------------| -- |
| getData() | String | | Returns the metadata associated to the token |
| getRole() | OpenViduRole | | Returns the role associated to the token |
##### **TokenOptions.Builder** _(inner static class)_
| Method | Returns | Parameters | Description |
| -------------- | ------- | --------------------------------------------- | ----------- |
| TokenOptions.Builder() | | | Constructor |
| build() | TokenOptions | | Returns a new **TokenOptions** object with the stablished properties. Default values if methods _data()_ and _role()_ are not called are an empty string and OpenViduRole.PUBLISHER, respectively |
| data() | TokenOptions.Builder | `String:data` | Some extra metadata to be associated to the user through its token. The structure of this string is up to you (maybe some standarized format as JSON or XML is a good idea), the only restriction is a maximum length of 1000 chars |
| role() | TokenOptions.Builder | `OpenViduRole:role` | The role associated to this token |
----------
Deploying on AWS
===================
Here you have a step by step guide to deploy a production version of OpenVidu in an Ubuntu machine. In this case, KMS and openvidu-server run in the same machine, the first one as a native service and the second one in a Docker container.
1. Install KMS (in first command: ***xenial*** for 16.04, ***trusty*** for 14.04)
```bash
echo "deb http://ubuntu.kurento.org xenial kms6" | sudo tee /etc/apt/sources.list.d/kurento.list
wget -O - http://ubuntu.kurento.org/kurento.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install kurento-media-server-6.0
```
2. Install COTURN
```
sudo apt-get install coturn
```
3. File `/etc/kurento/modules/kurento/WebRtcEndpoint.conf.ini`
```
stunServerAddress=STUN_IP
stunServerPort=STUN_PORT
turnURL=USER:PASS@YOUR_MACHINES'S_PUBLIC_IP:3478
```
4. File `/etc/turnserver.conf`
```
external-ip=YOUR_MACHINES'S_PUBLIC_IP
fingerprint
user=USER:PASS
lt-cred-mech
realm=kurento.org
log-file=/var/log/turnserver/turnserver.log
simple-log
```
5. File `/etc/default/coturn`
```
TURNSERVER_ENABLED=1
```
6. Init services
```bash
sudo service coturn restart
sudo service kurento-media-server-6.0 restart
```
7. Init openvidu-server Docker container (securization enabled)
```
sudo docker run -d -p 8443:8443 -e openvidu.security=true -e openvidu.secret=YOUR_SECRET -e kms.uris=[\"ws://YOUR_MACHINE'S_INTERNAL_IP:8888/kurento\"] openvidu/openvidu-server
```
----------
Developing OpenVidu
===================
Packages required:
```sudo apt-get update```
| Dependecy | Check version | Install |
| ------------- | ------------- |----------------------------------- |
| node | `nodejs -v` | `sudo apt-get install -g nodejs` |
| npm | `npm -v` | `sudo apt-get install -g npm` |
| maven | `mvn -v` | `sudo apt-get install -g maven` |
| angular-cli | `ng -v` | `sudo npm install -g @angular/cli` |
| typescript | `tsc -v` | `sudo npm install -g typescript` |
OpenVidu with KMS
------------------
How to *install* and *run* KMS in your development machine:
Ubuntu 14.04 LTS Trusty (64 bits)
```
echo "deb http://ubuntu.kurento.org trusty kms6" | sudo tee /etc/apt/sources.list.d/kurento.list
wget -O - http://ubuntu.kurento.org/kurento.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install kurento-media-server-6.0
```
Ubuntu 16.04 LTS Xenial (64 bits)
```
echo "deb http://ubuntu.kurento.org xenial kms6" | sudo tee /etc/apt/sources.list.d/kurento.list
wget -O - http://ubuntu.kurento.org/kurento.gpg.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install kurento-media-server-6.0
```
Start and stop the service
```
sudo service kurento-media-server-6.0 start
sudo service kurento-media-server-6.0 stop
```
[Here](http://doc-kurento.readthedocs.io/en/stable/installation_guide.html) you can check Kurento's official documentation.
Setup for development
------------------
Here we show how to develop an Angular app with OpenVidu having all packages linked in your local machine, so you can modify them and check the final result. After installing Kurento Media Server and forking or downloading the repo, these are the necessary steps to start developing **openvidu-ng-testapp**:
```
sudo service kurento-media-server-6.0 start
```
**/openvidu/openvidu-browser/src/main/resources**
```
npm install
sudo npm link
```
**/openvidu/openvidu-ng-testapp**
```
npm install
npm link openvidu-browser
ng serve
```
**/openvidu**
```
mvn compile -DskipTests=true
mvn install -DskipTests=true
```
**/openvidu/openvidu-server**
```
mvn clean compile package exec:java
```
*(or if you prefer you can just run the Java application in your favourite IDE)*
----------
At these point, you can start modifying *openvidu-ng-testapp*, *openvidu-browser* or *openvidu-server*.
- *openvidu-ng-testapp*: the previous "ng serve" command will take care of refreshing the browser's page whenever any change takes place.
- *openvidu-browser*: after modifying any typescript file, you will need to run the following command to update your changes (*typescript* package is necessary):
**/openvidu/openvidu-browser/src/main/resources**
```
npm run updatetsc
```
- *openvidu-server*: after modifying any file, there is no other alternative but to re-launch the java application if you want to update your changes.
**/openvidu/openvidu-server**
```
mvn clean compile package exec:java
```
*(or re-launch the Java application in your IDE. Some IDE's support automatic re-launch in response to changes)*
Setup for advanced development (publishing in local server)
------------------
You can also use different machines in the same network to build a more advanced development environment, so you can test the application in different devices at the same time. It's very similar to the process outlined above:
You will need a server for the built app (if you don't have any, we recommend *http-server*):
```npm install -g http-server```
Then...
```
sudo service kurento-media-server-6.0 start
```
**/openvidu/openvidu-browser/src/main/resources**
```
npm install
sudo npm link
```
**/openvidu/openvidu-ng-testapp**
```
npm install
npm link openvidu-browser
```
**/openvidu**
```
mvn compile -DskipTests=true
mvn install -DskipTests=true
```
**/openvidu/openvidu-server**
```
mvn clean compile package exec:java
```
*(or if you prefer you can just run the Java application in your favourite IDE)*
The following commands will be the ones which you should relaunch to update your changes:
**/openvidu/openvidu-ng-testapp**
```
ng build
cd dist
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem [ACCEPT ALL FIELDS]
http-server -S
```
These commands build the Angular project, generate a self-signed certificate (which unfortunately is a mandatory requirement for http-server SSL) and serves the content in http-server.
Finally, to launch the app connect to *https://127.0.0.1:8080* in the machine running the http-server and to *https://[HOST]:8080* in other devices of the same network ([HOST] the IP of the machine running the http-server).
Don't forget to accept the certificate at *https://[HOST]:8443* !
----------
Acknowledgments
===============
OpenVidu platform has been supported under project LERNIM (RTC-2016-4674-7) confunded by the _Ministry of Economy, Finance and Competitiveness_ of Spain, as well as by the _European Union_ FEDER, whose main goal with this funds is to promote technological development, innovation and high-quality research.
<p align="center">
<img width="400px" src="https://docs.google.com/uc?id=0B61cQ4sbhmWSQzNLQnF4SnhFLWc">
</p>
<p align="center">
<img width="400px" src="https://docs.google.com/uc?id=0B61cQ4sbhmWSa205YXNkSW9VNUE">
</p>
## Sponsors
Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/openvidu#sponsor)]
<a href="https://opencollective.com/openvidu/sponsor/0/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/0/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/1/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/1/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/2/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/2/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/3/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/3/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/4/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/4/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/5/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/5/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/6/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/6/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/7/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/7/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/8/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/8/avatar.svg"></a>
<a href="https://opencollective.com/openvidu/sponsor/9/website" target="_blank"><img src="https://opencollective.com/openvidu/sponsor/9/avatar.svg"></a>

18
ngrok-deploy-plainjs.sh Executable file
View File

@ -0,0 +1,18 @@
cd openvidu-browser/src/main/resources
npm run updatetsc
npm run browserify
cd ../../../../
# openvidu-sample-basic-plainjs
cp openvidu-browser/src/main/resources/static/js/OpenVidu.js ../openvidu-tutorials/openvidu-insecure-js/web/OpenVidu.js
cd ../openvidu-docker/openvidu-plainjs-demo
./create_image.sh
docker rm $(docker ps -q -f status=exited)
docker rmi $(docker images -q -f dangling=true)
docker run -p 5000:5000 -p 4040:4040 -e KMS_STUN_IP=193.147.51.12 -e KMS_STUN_PORT=3478 -e openvidu.security=false openvidu/openvidu-plainjs-demo

View File

@ -0,0 +1,3 @@
{
"directory" : "static/bower_components"
}

1
openvidu-browser/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

121
openvidu-browser/README.md Normal file
View File

@ -0,0 +1,121 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation badge](https://readthedocs.org/projects/fiware-orion/badge/?version=latest)](http://doc-kurento-room.readthedocs.org/en/latest/)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/fiware/stream-oriented-kurento/)
[![Support badge]( https://img.shields.io/badge/support-sof-yellowgreen.svg)](http://stackoverflow.com/questions/tagged/kurento)
[![][KurentoImage]][Kurento]
Copyright © 2013-2016 [Kurento]. Licensed under [Apache 2.0 License].
openvidu-browser
======================
openvidu-browser is a Javascript library that can be used to implement the client-side of group communications applications based on WebRTC.
It uses WebSockets and JSON-RPC to interact with the server-side of the Room API.
Typescript is currently used to develop openvidu-browser. The class diagram is shown below:
![OpenVidu structure](https://drive.google.com/uc?export=view&id=0B61cQ4sbhmWSM1N3SmE5amt3TzA)
What is Kurento
---------------
Kurento is an open source software project providing a platform suitable
for creating modular applications with advanced real-time communication
capabilities. For knowing more about Kurento, please visit the Kurento
project website: http://www.kurento.org.
Kurento is part of [FIWARE]. For further information on the relationship of
FIWARE and Kurento check the [Kurento FIWARE Catalog Entry]
Kurento is part of the [NUBOMEDIA] research initiative.
Documentation
-------------
The Kurento project provides detailed [documentation] including tutorials,
installation and development guides. A simplified version of the documentation
can be found on [readthedocs.org]. The [Open API specification] a.k.a. Kurento
Protocol is also available on [apiary.io].
Source
------
Code for other Kurento projects can be found in the [GitHub Kurento Group].
News and Website
----------------
Check the [Kurento blog]
Follow us on Twitter @[kurentoms].
Issue tracker
-------------
Issues and bug reports should be posted to the [GitHub Kurento bugtracker]
Licensing and distribution
--------------------------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contribution policy
-------------------
You can contribute to the Kurento community through bug-reports, bug-fixes, new
code or new documentation. For contributing to the Kurento community, drop a
post to the [Kurento Public Mailing List] providing full information about your
contribution and its value. In your contributions, you must comply with the
following guidelines
* You must specify the specific contents of your contribution either through a
detailed bug description, through a pull-request or through a patch.
* You must specify the licensing restrictions of the code you contribute.
* For newly created code to be incorporated in the Kurento code-base, you must
accept Kurento to own the code copyright, so that its open source nature is
guaranteed.
* You must justify appropriately the need and value of your contribution. The
Kurento project has no obligations in relation to accepting contributions
from third parties.
* The Kurento project leaders have the right of asking for further
explanations, tests or validations of any code contributed to the community
before it being incorporated into the Kurento code-base. You must be ready to
addressing all these kind of concerns before having your code approved.
Support
-------
The Kurento project provides community support through the [Kurento Public
Mailing List] and through [StackOverflow] using the tags *kurento* and
*fiware-kurento*.
Before asking for support, please read first the [Kurento Netiquette Guidelines]
[documentation]: http://www.kurento.org/documentation
[FIWARE]: http://www.fiware.org
[GitHub Kurento bugtracker]: https://github.com/Kurento/bugtracker/issues
[GitHub Kurento Group]: https://github.com/kurento
[kurentoms]: http://twitter.com/kurentoms
[Kurento]: http://kurento.org
[Kurento Blog]: http://www.kurento.org/blog
[Kurento FIWARE Catalog Entry]: http://catalogue.fiware.org/enablers/stream-oriented-kurento
[Kurento Netiquette Guidelines]: http://www.kurento.org/blog/kurento-netiquette-guidelines
[Kurento Public Mailing list]: https://groups.google.com/forum/#!forum/kurento
[KurentoImage]: https://secure.gravatar.com/avatar/21a2a12c56b2a91c8918d5779f1778bf?s=120
[Apache 2.0 License]: http://www.apache.org/licenses/LICENSE-2.0
[NUBOMEDIA]: http://www.nubomedia.eu
[StackOverflow]: http://stackoverflow.com/search?q=kurento
[Read-the-docs]: http://read-the-docs.readthedocs.org/
[readthedocs.org]: http://kurento.readthedocs.org/
[Open API specification]: http://kurento.github.io/doc-kurento/
[apiary.io]: http://docs.streamoriented.apiary.io/

119
openvidu-browser/pom.xml Normal file
View File

@ -0,0 +1,119 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.openvidu</groupId>
<artifactId>openvidu</artifactId>
<version>1.0.0-beta.1</version>
</parent>
<artifactId>openvidu-browser</artifactId>
<packaging>jar</packaging>
<name>OpenVidu Browser</name>
<description>
OpenVidu JS/TS library for the client-side
</description>
<url>https://github.com/OpenVidu/openvidu</url>
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<organization>
<name>OpenVidu</name>
<url>https://github.com/OpenVidu/openvidu</url>
</organization>
<scm>
<url>${openvidu.scm.url}</url>
<connection>scm:git:${openvidu.scm.connection}</connection>
<developerConnection>scm:git:${openvidu.scm.connection}</developerConnection>
<tag>develop</tag>
</scm>
<developers>
<developer>
<id>openvidu.io</id>
<name>-openvidu.io Community</name>
<organization>OpenVidu</organization>
<organizationUrl>http://openvidu.io</organizationUrl>
</developer>
</developers>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<configuration>
<mainClass>${start-class}</mainClass>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
</resources>
</build>
<dependencies>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-utils-js</artifactId>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-js</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>adapter.js</artifactId>
</dependency>
<dependency>
<groupId>org.webjars.bower</groupId>
<artifactId>EventEmitter.js</artifactId>
</dependency>
</dependencies>
<profiles>
<profile>
<id>default</id>
<activation>
<property>
<name>default</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,4 @@
/node_modules/
/yarn.lock
/npm-debug.log
/lib/

View File

@ -0,0 +1,26 @@
{
"name": "openvidu-browser",
"version": "1.0.3-beta.1",
"description": "OpenVidu Browser",
"main": "lib/OpenVidu/index.js",
"types": "lib/OpenVidu/index.d.ts",
"scripts": {
"browserify": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../../static/js/openvidu-browser-$VERSION.js -v",
"browserify-prod": "VERSION=${VERSION:-}; cd ts/OpenVidu && browserify --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | uglifyjs --source-map content=inline --output ../../static/js/openvidu-browser-$VERSION.min.js",
"updatetsc": "cd ts/OpenViduInternal && tsc && cd ../OpenVidu && tsc",
"test": "echo \"Error: no test specified\" && exit 1",
"prepublish": "cd ts && tsc",
"developing": "cd ts && tsc -w"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"kurento-jsonrpc": "5.1.3",
"wolfy87-eventemitter": "4.2.9",
"@types/wolfy87-eventemitter": "4.2.31",
"webrtc-adapter": "3.3.2",
"kurento-utils": "6.6.2",
"uuid": "~2.0.1",
"sdp-translator": "^0.1.15"
}
}

View File

@ -0,0 +1,11 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "0.1.0",
"command": "tsc",
"isShellCommand": true,
"args": ["-w", "-p", "."],
"showOutput": "silent",
"isWatching": true,
"problemMatcher": "$tsc-watch"
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
console.error("Error: Cannot find module 'kurento-browser-extensions' from '/home/mica/Data/Kurento/OpenVidu/git2/openvidu/openvidu-browser/src/main/resources/node_modules/kurento-utils/lib'");

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,12 @@
import { OpenVidu } from './OpenVidu';
//This export with --standalone option allows using OpenVidu from bowser with namespace
//export { OpenVidu } from './OpenVidu';
//This "hack" allows to use OpenVidu from the global space window
if(window){
window["OpenVidu"] = OpenVidu;
}
//Command to generate bundle.js without namespace
//watchify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/OpenVidu.js -v

View File

@ -0,0 +1,108 @@
/*
* (C) Copyright 2016 OpenVidu (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { OpenViduInternal } from '../OpenViduInternal/OpenViduInternal';
import { Session } from './Session';
import { Publisher } from './Publisher';
import * as adapter from 'webrtc-adapter';
if (window) {
window["adapter"] = adapter;
}
export class OpenVidu {
openVidu: OpenViduInternal;
constructor() {
this.openVidu = new OpenViduInternal();
};
initSession(apiKey: string, sessionId: string): Session;
initSession(sessionId: string): Session;
initSession(param1, param2?): any {
if (this.checkSystemRequirements()) {
if (typeof param2 == "string") {
return new Session(this.openVidu.initSession(param2), this);
} else {
return new Session(this.openVidu.initSession(param1), this);
}
} else {
alert("Browser not supported");
}
}
initPublisher(parentId: string): Publisher;
initPublisher(parentId: string, cameraOptions: any): Publisher;
initPublisher(parentId: string, cameraOptions: any, callback: any): Publisher;
initPublisher(parentId: string, cameraOptions?: any, callback?: Function): any {
if (this.checkSystemRequirements()) {
if (cameraOptions != null) {
let cameraOptionsAux = {
audio: cameraOptions.audio != null ? cameraOptions.audio : true,
video: cameraOptions.video != null ? cameraOptions.video : true,
data: true,
mediaConstraints: this.openVidu.generateMediaConstraints(cameraOptions.quality)
};
cameraOptions = cameraOptionsAux;
} else {
cameraOptions = {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
}
return new Publisher(this.openVidu.initPublisherTagged(parentId, cameraOptions, callback), parentId);
} else {
alert("Browser not supported");
}
}
checkSystemRequirements(): number {
let browser = adapter.browserDetails.browser;
let version = adapter.browserDetails.version;
//Bug fix: 'navigator.userAgent' in Firefox for Ubuntu 14.04 does not return "Firefox/[version]" in the string, so version returned is null
if ((browser == 'firefox') && (version == null)) {
return 1;
}
if (((browser == 'chrome') && (version >= 28)) || ((browser == 'edge') && (version >= 12)) || ((browser == 'firefox') && (version >= 22))) {
return 1;
} else {
return 0;
}
}
getDevices(callback) {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
callback(null, deviceInfos);
}).catch((error) => {
console.log("Error getting devices: " + error);
callback(error, null);
});
}
}

View File

@ -0,0 +1,103 @@
/*
* options: name: XXX data: true (Maybe this is based on webrtc) audio: true,
* video: true, url: "file:///..." > Player screen: true > Desktop (implicit
* video:true, audio:false) audio: true, video: true > Webcam
*
* stream.hasAudio(); stream.hasVideo(); stream.hasData();
*/
import { Stream, StreamOptions, VideoOptions } from '../OpenViduInternal/Stream';
import { Session } from './Session';
import EventEmitter = require('wolfy87-eventemitter');
export class Publisher {
private ee = new EventEmitter();
accessAllowed = false;
element: Element;
id: string;
stream: Stream;
session: Session; //Initialized by Session.publish(Publisher)
constructor(stream: Stream, parentId: string) {
this.stream = stream;
this.stream.addEventListener('camera-access-changed', (event) => {
this.accessAllowed = event.accessAllowed;
if (this.accessAllowed) {
this.ee.emitEvent('accessAllowed');
} else {
this.ee.emitEvent('accessDenied');
}
});
if (document.getElementById(parentId) != null) {
this.element = document.getElementById(parentId)!!;
}
}
publishAudio(value: boolean) {
this.stream.getWebRtcPeer().audioEnabled = value;
}
publishVideo(value: boolean) {
this.stream.getWebRtcPeer().videoEnabled = value;
}
destroy() {
this.session.unpublish(this);
this.stream.dispose();
this.stream.removeVideo(this.element);
return this;
}
on(eventName: string, callback) {
this.ee.addListener(eventName, event => {
callback(event);
});
if (eventName == 'videoElementCreated') {
if (this.stream.isVideoELementCreated) {
this.ee.emitEvent('videoElementCreated', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addEventListener('video-element-created-by-stream', (element) => {
console.warn('Publisher emitting videoElementCreated');
this.id = element.id;
this.ee.emitEvent('videoElementCreated', [{
element: element.element
}]);
});
}
}
if (eventName == 'streamCreated') {
if (this.stream.isReady) {
this.ee.emitEvent('streamCreated', [{ stream: this.stream }]);
} else {
this.stream.addEventListener('stream-created-by-publisher', () => {
console.warn('Publisher emitting streamCreated');
this.ee.emitEvent('streamCreated', [{ stream: this.stream }]);
});
}
}
if (eventName == 'accessAllowed') {
if (this.stream.accessIsAllowed) {
this.ee.emitEvent('accessAllowed');
} else {
this.stream.addEventListener('access-allowed-by-publisher', () => {
this.ee.emitEvent('accessAllowed');
});
}
}
if (eventName == 'accessDenied') {
if (this.stream.accessIsDenied) {
this.ee.emitEvent('accessDenied');
} else {
this.stream.addEventListener('access-denied-by-publisher', () => {
this.ee.emitEvent('accessDenied');
});
}
}
}
}

View File

@ -0,0 +1,179 @@
import { SessionInternal, SessionOptions } from '../OpenViduInternal/SessionInternal';
import { Stream } from '../OpenViduInternal/Stream';
import { Connection } from "../OpenViduInternal/Connection";
import { OpenVidu } from './OpenVidu';
import { Publisher } from './Publisher';
import { Subscriber } from './Subscriber';
import EventEmitter = require('wolfy87-eventemitter');
export class Session {
sessionId: String;
//capabilities: Capabilities
connection: Connection;
private ee = new EventEmitter();
constructor(private session: SessionInternal, private openVidu: OpenVidu) {
this.sessionId = session.getSessionId();
// Listens to the deactivation of the default behaviour upon the deletion of a Stream object
this.session.addEventListener('stream-destroyed-default', event => {
event.stream.removeVideo();
});
// Listens to the deactivation of the default behaviour upon the disconnection of a Session
this.session.addEventListener('session-disconnected-default', () => {
let s: Stream;
for (s of this.openVidu.openVidu.getRemoteStreams()) {
s.removeVideo();
}
if (this.connection) {
for (let streamId in this.connection.getStreams()) {
this.connection.getStreams()[streamId].removeVideo();
}
}
});
// Sets or updates the value of 'connection' property. Triggered by SessionInternal when succesful connection
this.session.addEventListener('update-connection-object', event => {
this.connection = event.connection;
});
}
connect(token: string, callback: any);
connect(token: string, metadata: string, callback: any);
connect(param1, param2, param3?) {
// Early configuration to deactivate automatic subscription to streams
if (typeof param2 == "string") {
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: param1,
metadata: param2,
subscribeToStreams: false
});
this.session.connect(param1, param3);
} else {
this.session.configure({
sessionId: this.session.getSessionId(),
participantId: param1,
metadata: '',
subscribeToStreams: false
});
this.session.connect(param1, param2);
}
}
disconnect() {
this.openVidu.openVidu.close(false);
this.session.emitEvent('sessionDisconnected', [{
preventDefault: () => { this.session.removeEvent('session-disconnected-default'); }
}]);
this.session.emitEvent('session-disconnected-default', [{}]);
}
publish(publisher: Publisher) {
publisher.session = this;
publisher.stream.publish();
}
unpublish(publisher: Publisher) {
this.session.unpublish(publisher.stream);
}
on(eventName: string, callback) {
this.session.addEventListener(eventName, event => {
callback(event);
});
}
once(eventName: string, callback) {
this.session.addOnceEventListener(eventName, event => {
callback(event);
});
}
off(eventName: string, eventHandler) {
this.session.removeListener(eventName, eventHandler);
}
subscribe(stream: Stream, htmlId: string, videoOptions: any): Subscriber;
subscribe(stream: Stream, htmlId: string): Subscriber;
subscribe(param1, param2, param3?): Subscriber {
// Subscription
this.session.subscribe(param1);
let subscriber = new Subscriber(param1, param2);
param1.playOnlyVideo(param2, null);
return subscriber;
}
unsubscribe(subscriber: Subscriber) {
this.session.unsuscribe(subscriber.stream);
subscriber.stream.removeVideo();
}
/* Shortcut event API */
onStreamCreated(callback) {
this.session.addEventListener("streamCreated", streamEvent => {
callback(streamEvent.stream);
});
}
onStreamDestroyed(callback) {
this.session.addEventListener("streamDestroyed", streamEvent => {
callback(streamEvent.stream);
});
}
onParticipantJoined(callback) {
this.session.addEventListener("participant-joined", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantLeft(callback) {
this.session.addEventListener("participant-left", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantPublished(callback) {
this.session.addEventListener("participant-published", participantEvent => {
callback(participantEvent.connection);
});
}
onParticipantEvicted(callback) {
this.session.addEventListener("participant-evicted", participantEvent => {
callback(participantEvent.connection);
});
}
onRoomClosed(callback) {
this.session.addEventListener("room-closed", roomEvent => {
callback(roomEvent.room);
});
}
onLostConnection(callback) {
this.session.addEventListener("lost-connection", roomEvent => {
callback(roomEvent.room);
});
}
onMediaError(callback) {
this.session.addEventListener("error-media", errorEvent => {
callback(errorEvent.error)
});
}
/* Shortcut event API */
}

View File

@ -0,0 +1,40 @@
import { Stream, StreamOptions, VideoOptions } from '../OpenViduInternal/Stream';
import EventEmitter = require('wolfy87-eventemitter');
export class Subscriber {
private ee = new EventEmitter();
element: Element;
id: string;
stream: Stream;
constructor(stream: Stream, parentId: string) {
this.stream = stream;
if (document.getElementById(parentId) != null) {
this.element = document.getElementById(parentId)!!;
}
}
on(eventName: string, callback) {
this.ee.addListener(eventName, event => {
callback(event);
});
if (eventName == 'videoElementCreated') {
if (this.stream.isReady) {
this.ee.emitEvent('videoElementCreated', [{
element: this.stream.getVideoElement()
}]);
} else {
this.stream.addEventListener('video-element-created-by-stream', element => {
console.warn("Subscriber emitting videoElementCreated");
this.id = element.id;
this.ee.emitEvent('videoElementCreated', [{
element: element
}]);
});
}
}
}
}

View File

@ -0,0 +1,3 @@
declare module "kurento-jsonrpc";
declare module "webrtc-adapter";
declare module "kurento-utils";

View File

@ -0,0 +1,6 @@
export * from './OpenVidu';
export * from './Session';
export * from './Publisher';
export * from './Subscriber';
export * from '../OpenViduInternal/Stream';
export * from '../OpenViduInternal/Connection';

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"declaration": true,
"target": "es5",
"module": "commonjs",
//"noImplicitAny": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
//"allowUnusedLabels": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
//"allowUnreachableCode": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"outDir": "../../lib",
"emitBOM": false,
"preserveConstEnums": true,
"sourceMap": true
},
//"buildOnSave": true,
"compileOnSave":true
}

View File

@ -0,0 +1,86 @@
import { Stream, StreamOptions } from './Stream';
import { OpenViduInternal } from './OpenViduInternal';
import { SessionInternal } from './SessionInternal';
type ObjMap<T> = { [s: string]: T; }
export interface ConnectionOptions {
id: string;
metadata: string;
streams?: StreamOptions[];
}
export class Connection {
public connectionId: string;
public data: string;
public creationTime: number;
private streams: ObjMap<Stream> = {};
private streamsOpts: StreamOptions[] = [];
constructor( private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, private options?: ConnectionOptions ) {
if ( options ) {
this.connectionId = options.id;
this.data = options.metadata;
if ( options.streams ) {
for ( let streamOptions of options.streams ) {
let streamOpts = {
id: streamOptions.id,
connection: this,
recvVideo: ( streamOptions.recvVideo == undefined ? true : streamOptions.recvVideo ),
recvAudio: ( streamOptions.recvAudio == undefined ? true : streamOptions.recvAudio ),
audio: streamOptions.audio,
video: streamOptions.video,
data: streamOptions.data,
mediaConstraints: streamOptions.mediaConstraints
}
let stream = new Stream( openVidu, false, room, streamOpts );
this.addStream( stream );
this.streamsOpts.push( streamOpts );
}
}
}
console.log( "New " + ( local ? "local " : "remote " ) + "participant " + this.connectionId
+ ", streams opts: ", this.streamsOpts );
}
addStream( stream: Stream ) {
this.streams[stream.getIdInParticipant()] = stream;
this.room.getStreams()[stream.getIdInParticipant()] = stream;
}
getStreams() {
return this.streams;
}
dispose() {
for ( let key in this.streams ) {
this.streams[key].dispose();
}
}
sendIceCandidate( candidate ) {
console.debug(( this.local ? "Local" : "Remote" ), "candidate for",
this.connectionId, JSON.stringify( candidate ) );
this.openVidu.sendRequest( "onIceCandidate", {
endpointName: this.connectionId,
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, function( error, response ) {
if ( error ) {
console.error( "Error sending ICE candidate: "
+ JSON.stringify( error ) );
}
});
}
}

View File

@ -0,0 +1,12 @@
import { OpenViduInternal } from './OpenViduInternal';
//This export with --standalone option allows using OpenVidu from bowser with namespace
//export { OpenVidu } from './OpenVidu';
//This "hack" allows to use OpenVidu from the global space window
if(window){
window["OpenViduInternal"] = OpenViduInternal;
}
//Command to generate bundle.js without namespace
//watchify Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/OpenVidu.js -v

View File

@ -0,0 +1,388 @@
/*
* (C) Copyright 2016 OpenVidu (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { SessionInternal, SessionOptions } from './SessionInternal';
import { Stream } from './Stream';
import * as RpcBuilder from 'kurento-jsonrpc';
export type Callback<T> = (error?: any, openVidu?: T) => void;
export class OpenViduInternal {
private wsUri;
private session: SessionInternal;
private jsonRpcClient: any;
private rpcParams: any;
private callback: Callback<OpenViduInternal>;
private camera: Stream;
private remoteStreams: Stream[] = [];
constructor() { };
/* NEW METHODS */
initSession(sessionId) {
console.log("Session initialized!");
this.session = new SessionInternal(this, sessionId);
return this.session;
}
initPublisherTagged(parentId: string, cameraOptions: any, callback?) {
console.log("Publisher tagged initialized!");
this.getCamera(cameraOptions);
if (callback == null) {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
console.log("Error accessing the camera");
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
}
});
return this.camera;
} else {
this.camera.requestCameraAccess((error, camera) => {
if (error) {
callback(error);
}
else {
this.camera.setVideoElement(this.cameraReady(camera!, parentId));
callback(undefined);
}
});
return this.camera;
}
}
cameraReady(camera: Stream, parentId: string) {
this.camera = camera;
let videoElement = this.camera.playOnlyVideo(parentId, null);
this.camera.emitStreamReadyEvent();
return videoElement;
}
initPublisher(cameraOptions: any, callback) {
console.log("Publisher initialized!");
this.getCamera(cameraOptions);
this.camera.requestCameraAccess((error, camera) => {
if (error) callback(error);
else callback(undefined);
});
}
getLocalStream() {
return this.camera;
}
getRemoteStreams() {
return this.remoteStreams;
}
/* NEW METHODS */
getWsUri() {
return this.wsUri;
}
setWsUri(wsUri: string) {
this.wsUri = wsUri;
}
getOpenViduServerURL() {
return 'https://' + this.wsUri.split("wss://")[1].split("/room")[0];
}
getRoom() {
return this.session;
}
connect(callback: Callback<OpenViduInternal>): void {
this.callback = callback;
this.initJsonRpcClient(this.wsUri);
}
private initJsonRpcClient(wsUri: string): void {
let config = {
heartbeat: 3000,
sendCloseMessage: false,
ws: {
uri: wsUri,
useSockJS: false,
onconnected: this.connectCallback.bind(this),
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
},
rpc: {
requestTimeout: 15000,
//notifications
participantJoined: this.onParticipantJoined.bind(this),
participantPublished: this.onParticipantPublished.bind(this),
participantUnpublished: this.onParticipantLeft.bind(this),
participantLeft: this.onParticipantLeft.bind(this),
participantEvicted: this.onParticipantEvicted.bind(this),
sendMessage: this.onNewMessage.bind(this),
iceCandidate: this.iceCandidateEvent.bind(this),
mediaError: this.onMediaError.bind(this),
custonNotification: this.customNotification.bind(this)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
}
private customNotification(params) {
if (this.isRoomAvailable()) {
this.session.emitEvent("custom-message-received", [{ params: params }]);
}
}
private connectCallback(error) {
if (error) {
this.callback(error);
} else {
this.callback(null);
}
}
private isRoomAvailable() {
if (this.session !== undefined && this.session instanceof SessionInternal) {
return true;
} else {
console.warn('Room instance not found');
return false;
}
}
private disconnectCallback() {
console.log('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectingCallback() {
console.log('Websocket connection lost (reconnecting)');
if (this.isRoomAvailable()) {
this.session.onLostConnection();
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectedCallback() {
console.log('Websocket reconnected');
}
private onParticipantJoined(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantJoined(params);
}
}
private onParticipantPublished(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantPublished(params);
}
}
private onParticipantLeft(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantLeft(params);
}
}
private onParticipantEvicted(params) {
if (this.isRoomAvailable()) {
this.session.onParticipantEvicted(params);
}
}
private onNewMessage(params) {
if (this.isRoomAvailable()) {
this.session.onNewMessage(params);
}
}
private iceCandidateEvent(params) {
if (this.isRoomAvailable()) {
this.session.recvIceCandidate(params);
}
}
private onRoomClosed(params) {
if (this.isRoomAvailable()) {
this.session.onRoomClosed(params);
}
}
private onMediaError(params) {
if (this.isRoomAvailable()) {
this.session.onMediaError(params);
}
}
setRpcParams(params: any) {
this.rpcParams = params;
}
sendRequest(method, params, callback?) {
if (params && params instanceof Function) {
callback = params;
params = undefined;
}
params = params || {};
if (this.rpcParams && this.rpcParams !== null && this.rpcParams !== undefined) {
for (let index in this.rpcParams) {
if (this.rpcParams.hasOwnProperty(index)) {
params[index] = this.rpcParams[index];
console.log('RPC param added to request {' + index + ': ' + this.rpcParams[index] + '}');
}
}
}
console.log('Sending request: { method:"' + method + '", params: ' + JSON.stringify(params) + ' }');
this.jsonRpcClient.send(method, params, callback);
}
close(forced) {
if (this.isRoomAvailable()) {
this.session.leave(forced, this.jsonRpcClient);
}
};
disconnectParticipant(stream) {
if (this.isRoomAvailable()) {
this.session.disconnect(stream);
}
}
getCamera(options?) {
if (this.camera) {
return this.camera;
}
options = options || {
audio: true,
video: true,
data: true,
mediaConstraints: {
audio: true,
video: { width: { ideal: 1280 } }
}
}
options.connection = this.session.getLocalParticipant();
this.camera = new Stream(this, true, this.session, options);
return this.camera;
};
/*joinSession(options: SessionOptions, callback: Callback<Session>) {
this.session.configure(options);
this.session.connect2();
this.session.addEventListener('room-connected', roomEvent => callback(undefined,this.session));
this.session.addEventListener('error-room', error => callback(error));
return this.session;
};*/
//CHAT
sendMessage(room, user, message) {
this.sendRequest('sendMessage', {
message: message,
userMessage: user,
roomMessage: room
}, function (error, response) {
if (error) {
console.error(error);
}
});
};
sendCustomRequest(params, callback) {
this.sendRequest('customRequest', params, callback);
};
toggleLocalVideoTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().videoEnabled = activate;
}
toggleLocalAudioTrack(activate: boolean) {
this.getCamera().getWebRtcPeer().audioEnabled = activate;
}
publishLocalVideoAudio() {
this.toggleLocalVideoTrack(true);
this.toggleLocalAudioTrack(true);
}
unpublishLocalVideoAudio() {
this.toggleLocalVideoTrack(false);
this.toggleLocalAudioTrack(false);
}
generateMediaConstraints(quality: string) {
let mediaConstraints = {
audio: true,
video: {}
}
let w, h;
switch (quality) {
case 'LOW':
w = 320;
h = 240;
break;
case 'MEDIUM':
w = 640;
h = 480;
break;
case 'HIGH':
w = 1280;
h = 720;
break;
default:
w = 640;
h = 480;
}
mediaConstraints.video['width'] = { exact: w };
mediaConstraints.video['height'] = { exact: h };
//mediaConstraints.video['frameRate'] = { ideal: Number((<HTMLInputElement>document.getElementById('frameRate')).value) };
return mediaConstraints;
}
}

View File

@ -0,0 +1,519 @@
import { Stream } from './Stream';
import { OpenViduInternal } from './OpenViduInternal';
import { Connection, ConnectionOptions } from './Connection';
import EventEmitter = require('wolfy87-eventemitter');
export interface SessionOptions {
sessionId: string;
participantId: string;
metadata: string;
subscribeToStreams?: boolean;
updateSpeakerInterval?: number;
thresholdSpeaker?: number;
}
export class SessionInternal {
private id: string;
private ee = new EventEmitter();
private streams = {};
private participants = {};
private participantsSpeaking: Connection[] = [];
private connected = false;
public localParticipant: Connection;
private subscribeToStreams: boolean;
private updateSpeakerInterval: number;
public thresholdSpeaker: number;
private options: SessionOptions
constructor(private openVidu: OpenViduInternal, private sessionId: string) {
this.localParticipant = new Connection(this.openVidu, true, this);
if (!this.openVidu.getWsUri()) {
this.openVidu.setWsUri(this.checkNgrokUri(sessionId));
}
}
checkNgrokUri(sessionId: string): string {
sessionId = sessionId.substring(0, sessionId.lastIndexOf('/')) + '/room';
if (sessionId.indexOf(".ngrok.io") !== -1) {
// OpenVidu server URL referes to a ngrok IP: secure wss protocol and delete port of URL
sessionId = sessionId.replace("ws://", "wss://");
let regex = /\.ngrok\.io:\d+/;
sessionId = sessionId.replace(regex, ".ngrok.io");
} else if ((sessionId.indexOf("localhost") !== -1) || (sessionId.indexOf("127.0.0.1") != -1)) {
// OpenVidu server URL referes to localhost IP
}
return sessionId;
}
/* NEW METHODS */
connect(token, callback) {
this.openVidu.connect((error) => {
if (error) {
callback('ERROR CONNECTING TO OPENVIDU');
}
else {
let joinParams = {
token: token,
session: this.sessionId,
metadata: this.options.metadata,
dataChannels: false
}
if (this.localParticipant) {
if (Object.keys(this.localParticipant.getStreams()).some(streamId =>
this.streams[streamId].isDataChannelEnabled())) {
joinParams.dataChannels = true;
}
}
this.openVidu.sendRequest('joinRoom', joinParams, (error, response) => {
if (error) {
callback('UNABLE TO JOIN ROOM');
} else {
this.connected = true;
let exParticipants = response.value;
// IMPORTANT: Update connectionId with value send by server
this.localParticipant.connectionId = response.id;
this.participants[response.id] = this.localParticipant;
let roomEvent = {
participants: new Array<Connection>(),
streams: new Array<Stream>()
}
let length = exParticipants.length;
for (let i = 0; i < length; i++) {
let connection = new Connection(this.openVidu, false, this,
exParticipants[i]);
connection.creationTime = new Date().getTime();
this.participants[connection.connectionId] = connection;
roomEvent.participants.push(connection);
let streams = connection.getStreams();
for (let key in streams) {
roomEvent.streams.push(streams[key]);
if (this.subscribeToStreams) {
streams[key].subscribe();
}
}
}
// Update local Connection object properties with values returned by server
this.localParticipant.data = response.metadata;
this.localParticipant.creationTime = new Date().getTime();
// Updates the value of property 'connection' in Session object
this.ee.emitEvent('update-connection-object', [{ connection: this.localParticipant }]);
// Own connection created event
this.ee.emitEvent('connectionCreated', [{ connection: this.localParticipant }]);
// One connection created event for each existing connection in the session
for (let part of roomEvent.participants) {
this.ee.emitEvent('connectionCreated', [{ connection: part }]);
}
//if (this.subscribeToStreams) {
for (let stream of roomEvent.streams) {
this.ee.emitEvent('streamCreated', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
//}
callback(undefined);
}
});
}
});
}
publish() {
this.openVidu.getCamera().publish();
}
/* NEW METHODS */
configure(options: SessionOptions) {
this.options = options;
this.id = options.sessionId;
this.subscribeToStreams = options.subscribeToStreams == null ? true : options.subscribeToStreams;
this.updateSpeakerInterval = options.updateSpeakerInterval || 1500;
this.thresholdSpeaker = options.thresholdSpeaker || -50;
this.activateUpdateMainSpeaker();
}
getId() {
return this.id;
}
getSessionId() {
return this.sessionId;
}
private activateUpdateMainSpeaker() {
setInterval(() => {
if (this.participantsSpeaking.length > 0) {
this.ee.emitEvent('update-main-speaker', [{
participantId: this.participantsSpeaking[this.participantsSpeaking.length - 1]
}]);
}
}, this.updateSpeakerInterval);
}
getLocalParticipant() {
return this.localParticipant;
}
addEventListener(eventName, listener) {
this.ee.on(eventName, listener);
}
addOnceEventListener(eventName, listener) {
this.ee.once(eventName, listener);
}
removeListener(eventName, listener) {
this.ee.off(eventName, listener);
}
removeEvent(eventName) {
this.ee.removeEvent(eventName);
}
emitEvent(eventName, eventsArray) {
this.ee.emitEvent(eventName, eventsArray);
}
subscribe(stream: Stream) {
stream.subscribe();
}
unsuscribe(stream) {
console.log("Unsubscribing from " + stream.getId());
this.openVidu.sendRequest('unsubscribeFromVideo', {
sender: stream.getId()
},
function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Unsubscribed correctly from " + stream.getId());
}
});
}
onParticipantPublished(options) {
options.metadata = this.participants[options.id].data;
let connection = new Connection(this.openVidu, false, this, options);
let pid = connection.connectionId;
if (!(pid in this.participants)) {
console.info("Publisher not found in participants list by its id", pid);
} else {
console.log("Publisher found in participants list by its id", pid);
}
//replacing old connection (this one has streams)
connection.creationTime = this.participants[pid].creationTime;
this.participants[pid] = connection;
this.ee.emitEvent('participant-published', [{ connection }]);
let streams = connection.getStreams();
for (let key in streams) {
let stream = streams[key];
if (this.subscribeToStreams) {
stream.subscribe();
}
this.ee.emitEvent('streamCreated', [{ stream }]);
// Adding the remote stream to the OpenVidu object
this.openVidu.getRemoteStreams().push(stream);
}
}
onParticipantJoined(msg) {
let connection = new Connection(this.openVidu, false, this, msg);
connection.creationTime = new Date().getTime();
let pid = connection.connectionId;
if (!(pid in this.participants)) {
console.log("New participant to participants list with id", pid);
this.participants[pid] = connection;
} else {
//use existing so that we don't lose streams info
console.info("Participant already exists in participants list with " +
"the same id, old:", this.participants[pid], ", joined now:", connection);
connection = this.participants[pid];
}
this.ee.emitEvent('participant-joined', [{
connection: connection
}]);
this.ee.emitEvent('connectionCreated', [{
connection: connection
}]);
}
onParticipantLeft(msg) {
let connection = this.participants[msg.name];
if (connection !== undefined) {
delete this.participants[msg.name];
this.ee.emitEvent('participant-left', [{
connection: connection
}]);
let streams = connection.getStreams();
for (let key in streams) {
this.ee.emitEvent('streamDestroyed', [{
stream: streams[key],
preventDefault: () => { this.ee.removeEvent('stream-destroyed-default'); }
}]);
this.ee.emitEvent('stream-destroyed-default', [{
stream: streams[key]
}]);
// Deleting the removed stream from the OpenVidu object
let index = this.openVidu.getRemoteStreams().indexOf(streams[key]);
this.openVidu.getRemoteStreams().splice(index, 1);
}
connection.dispose();
this.ee.emitEvent('connectionDestroyed', [{
connection: connection
}]);
} else {
console.warn("Participant " + msg.name
+ " unknown. Participants: "
+ JSON.stringify(this.participants));
}
};
onParticipantEvicted(msg) {
this.ee.emitEvent('participant-evicted', [{
localParticipant: this.localParticipant
}]);
};
onNewMessage(msg) {
console.log("New message: " + JSON.stringify(msg));
let room = msg.room;
let user = msg.user;
let message = msg.message;
if (user !== undefined) {
this.ee.emitEvent('newMessage', [{
room: room,
user: user,
message: message
}]);
} else {
console.warn("User undefined in new message:", msg);
}
}
recvIceCandidate(msg) {
let candidate = {
candidate: msg.candidate,
sdpMid: msg.sdpMid,
sdpMLineIndex: msg.sdpMLineIndex
}
let connection = this.participants[msg.endpointName];
if (!connection) {
console.error("Participant not found for endpoint " +
msg.endpointName + ". Ice candidate will be ignored.",
candidate);
return;
}
let streams = connection.getStreams();
for (let key in streams) {
let stream = streams[key];
stream.getWebRtcPeer().addIceCandidate(candidate, function (error) {
if (error) {
console.error("Error adding candidate for " + key
+ " stream of endpoint " + msg.endpointName
+ ": " + error);
}
});
}
}
onRoomClosed(msg) {
console.log("Room closed: " + JSON.stringify(msg));
let room = msg.room;
if (room !== undefined) {
this.ee.emitEvent('room-closed', [{
room: room
}]);
} else {
console.warn("Room undefined in on room closed", msg);
}
}
onLostConnection() {
if (!this.connected) {
console.warn('Not connected to room: if you are not debugging, this is probably a certificate error');
if (window.confirm('If you are not debugging, this is probably a certificate error at \"' + this.openVidu.getOpenViduServerURL() + '\"\n\nClick OK to navigate and accept it')) {
location.assign(this.openVidu.getOpenViduServerURL() + '/accept-certificate');
};
return;
}
console.log('Lost connection in room ' + this.id);
let room = this.id;
if (room !== undefined) {
this.ee.emitEvent('lost-connection', [{ room }]);
} else {
console.warn('Room undefined when lost connection');
}
}
onMediaError(params) {
console.error("Media error: " + JSON.stringify(params));
let error = params.error;
if (error) {
this.ee.emitEvent('error-media', [{
error: error
}]);
} else {
console.warn("Received undefined media error. Params:", params);
}
}
/*
* forced means the user was evicted, no need to send the 'leaveRoom' request
*/
leave(forced, jsonRpcClient) {
forced = !!forced;
console.log("Leaving room (forced=" + forced + ")");
if (this.connected && !forced) {
this.openVidu.sendRequest('leaveRoom', function (error, response) {
if (error) {
console.error(error);
}
jsonRpcClient.close();
});
} else {
jsonRpcClient.close();
}
this.connected = false;
if (this.participants) {
for (let pid in this.participants) {
this.participants[pid].dispose();
delete this.participants[pid];
}
}
}
disconnect(stream: Stream) {
let connection = stream.getParticipant();
if (!connection) {
console.error("Stream to disconnect has no participant", stream);
return;
}
delete this.participants[connection.connectionId];
connection.dispose();
if (connection === this.localParticipant) {
console.log("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
} else {
this.unsuscribe(stream);
}
}
unpublish(stream: Stream) {
let connection = stream.getParticipant();
if (!connection) {
console.error("Stream to disconnect has no participant", stream);
return;
}
if (connection === this.localParticipant) {
delete this.participants[connection.connectionId];
connection.dispose();
console.log("Unpublishing my media (I'm " + connection.connectionId + ")");
delete this.localParticipant;
this.openVidu.sendRequest('unpublishVideo', function (error, response) {
if (error) {
console.error(error);
} else {
console.info("Media unpublished correctly");
}
});
}
}
getStreams() {
return this.streams;
}
addParticipantSpeaking(participantId) {
this.participantsSpeaking.push(participantId);
}
removeParticipantSpeaking(participantId) {
let pos = -1;
for (let i = 0; i < this.participantsSpeaking.length; i++) {
if (this.participantsSpeaking[i] == participantId) {
pos = i;
break;
}
}
if (pos != -1) {
this.participantsSpeaking.splice(pos, 1);
}
}
}

View File

@ -0,0 +1,650 @@
/*
* options: name: XXX data: true (Maybe this is based on webrtc) audio: true,
* video: true, url: "file:///..." > Player screen: true > Desktop (implicit
* video:true, audio:false) audio: true, video: true > Webcam
*
* stream.hasAudio(); stream.hasVideo(); stream.hasData();
*/
import { Connection } from './Connection';
import { SessionInternal } from './SessionInternal';
import { OpenViduInternal, Callback } from './OpenViduInternal';
import EventEmitter = require('wolfy87-eventemitter');
import * as kurentoUtils from 'kurento-utils';
import * as adapter from 'webrtc-adapter';
declare var navigator: any;
declare var RTCSessionDescription: any;
if (window) {
window["adapter"] = adapter;
}
function jq(id: string): string {
return id.replace(/(@|:|\.|\[|\]|,)/g, "\\$1");
}
function show(id: string) {
document.getElementById(jq(id))!.style.display = 'block';
}
function hide(id: string) {
document.getElementById(jq(id))!.style.display = 'none';
}
export interface StreamOptions {
id: string;
connection: Connection;
recvVideo: any;
recvAudio: any;
video: boolean;
audio: boolean;
data: boolean;
mediaConstraints: any;
}
export interface VideoOptions {
thumb: string;
video: HTMLVideoElement;
}
export class Stream {
public connection: Connection;
private ee = new EventEmitter();
private wrStream: any;
private wp: any;
private id: string;
private video: HTMLVideoElement;
private videoElements: VideoOptions[] = [];
private elements: HTMLDivElement[] = [];
private speechEvent: any;
private recvVideo: any;
private recvAudio: any;
private sendVideo: boolean;
private sendAudio: boolean;
private mediaConstraints: any;
private showMyRemote = false;
private localMirrored = false;
private chanId = 0;
private dataChannel: boolean;
private dataChannelOpened = false;
private videoSrc: string;
private parentId: string;
public isReady: boolean = false;
public isVideoELementCreated: boolean = false;
public accessIsAllowed: boolean = false;
public accessIsDenied: boolean = false;
constructor(private openVidu: OpenViduInternal, private local: boolean, private room: SessionInternal, options: StreamOptions) {
if (options.id) {
this.id = options.id;
} else {
this.id = "webcam";
}
this.connection = options.connection;
this.recvVideo = options.recvVideo;
this.recvAudio = options.recvAudio;
this.dataChannel = options.data || false;
this.sendVideo = options.video;
this.sendAudio = options.audio;
this.mediaConstraints = options.mediaConstraints;
this.addEventListener('src-added', (srcEvent) => {
this.videoSrc = srcEvent.src;
if (this.video) this.video.src = srcEvent.src;
console.warn("Videosrc [" + srcEvent.src + "] added to stream [" + this.getId() + "]");
});
}
emitSrcEvent(wrstream) {
this.ee.emitEvent('src-added', [{
src: URL.createObjectURL(wrstream)
}]);
}
emitStreamReadyEvent() {
this.ee.emitEvent('stream-ready'), [{}];
}
getVideoSrc() {
return this.videoSrc;
}
removeVideo(parentElement: string);
removeVideo(parentElement: Element);
removeVideo();
removeVideo(parentElement?) {
if (typeof parentElement === "string") {
document.getElementById(parentElement)!.removeChild(this.video);
} else if (parentElement instanceof Element) {
parentElement.removeChild(this.video);
}
else if (!parentElement) {
if (document.getElementById(this.parentId)) {
document.getElementById(this.parentId)!.removeChild(this.video);
}
}
}
getVideoElement(): HTMLVideoElement {
return this.video;
}
setVideoElement(video: HTMLVideoElement) {
this.video = video;
}
getRecvVideo() {
return this.recvVideo;
}
getRecvAudio() {
return this.recvAudio;
}
subscribeToMyRemote() {
this.showMyRemote = true;
}
displayMyRemote() {
return this.showMyRemote;
}
mirrorLocalStream(wr) {
this.showMyRemote = true;
this.localMirrored = true;
if (wr) {
this.wrStream = wr;
this.emitSrcEvent(this.wrStream);
}
}
isLocalMirrored() {
return this.localMirrored;
}
getChannelName() {
return this.getId() + '_' + this.chanId++;
}
isDataChannelEnabled() {
return this.dataChannel;
}
isDataChannelOpened() {
return this.dataChannelOpened;
}
onDataChannelOpen(event) {
console.log('Data channel is opened');
this.dataChannelOpened = true;
}
onDataChannelClosed(event) {
console.log('Data channel is closed');
this.dataChannelOpened = false;
}
sendData(data) {
if (this.wp === undefined) {
throw new Error('WebRTC peer has not been created yet');
}
if (!this.dataChannelOpened) {
throw new Error('Data channel is not opened');
}
console.log("Sending through data channel: " + data);
this.wp.send(data);
}
getWrStream() {
return this.wrStream;
}
getWebRtcPeer() {
return this.wp;
}
addEventListener(eventName: string, listener: any) {
this.ee.addListener(eventName, listener);
}
addOnceEventListener(eventName: string, listener: any) {
this.ee.addOnceListener(eventName, listener);
}
removeListener(eventName) {
this.ee.removeAllListeners(eventName);
}
showSpinner(spinnerParentId: string) {
let progress = document.createElement('div');
progress.id = 'progress-' + this.getId();
progress.style.background = "center transparent url('img/spinner.gif') no-repeat";
let spinnerParent = document.getElementById(spinnerParentId);
if (spinnerParent) {
spinnerParent.appendChild(progress);
}
}
hideSpinner(spinnerId?: string) {
spinnerId = (spinnerId === undefined) ? this.getId() : spinnerId;
hide('progress-' + spinnerId);
}
playOnlyVideo(parentElement, thumbnailId) {
// TO-DO: check somehow if the stream is audio only, so the element created is <audio> instead of <video>
this.video = document.createElement('video');
this.video.id = 'native-video-' + this.getId();
this.video.autoplay = true;
this.video.controls = false;
this.video.src = this.videoSrc;
this.videoElements.push({
thumb: thumbnailId,
video: this.video
});
if (this.local) {
this.video.muted = true;
} else {
this.video.title = this.getId();
}
if (typeof parentElement === "string") {
this.parentId = parentElement;
let parentElementDom = document.getElementById(parentElement);
if (parentElementDom) {
this.video = parentElementDom.appendChild(this.video);
this.ee.emitEvent('video-element-created-by-stream', [{
element: this.video
}]);
this.isVideoELementCreated = true;
}
} else {
this.parentId = parentElement.id;
this.video = parentElement.appendChild(this.video);
}
this.ee.emitEvent('stream-created-by-publisher');
this.isReady = true;
return this.video;
}
playThumbnail(thumbnailId) {
let container = document.createElement('div');
container.className = "participant";
container.id = this.getId();
let thumbnail = document.getElementById(thumbnailId);
if (thumbnail) {
thumbnail.appendChild(container);
}
this.elements.push(container);
let name = document.createElement('div');
container.appendChild(name);
let userName = this.getId().replace('_webcam', '');
if (userName.length >= 16) {
userName = userName.substring(0, 16) + "...";
}
name.appendChild(document.createTextNode(userName));
name.id = "name-" + this.getId();
name.className = "name";
name.title = this.getId();
this.showSpinner(thumbnailId);
return this.playOnlyVideo(container, thumbnailId);
}
getIdInParticipant() {
return this.id;
}
getParticipant() {
return this.connection;
}
getId() {
if (this.connection) {
return this.connection.connectionId + "_" + this.id;
} else {
return this.id + "_webcam";
}
}
getRTCPeerConnection() {
return this.getWebRtcPeer().peerConnection;
}
requestCameraAccess(callback: Callback<Stream>) {
this.connection.addStream(this);
let constraints = this.mediaConstraints;
/*let constraints2 = {
audio: true,
video: {
width: {
ideal: 1280
},
frameRate: {
ideal: 15
}
}
};*/
this.userMediaHasVideo((hasVideo) => {
if (!hasVideo) {
constraints.video = false;
this.sendVideo = false;
this.requestCameraAccesAux(constraints, callback);
} else {
this.requestCameraAccesAux(constraints, callback);
}
});
}
private requestCameraAccesAux(constraints, callback) {
navigator.mediaDevices.getUserMedia(constraints)
.then(userStream => {
this.accessIsAllowed = true;
this.accessIsDenied = false;
this.ee.emitEvent('access-allowed-by-publisher');
if (userStream.getAudioTracks()[0] != null) {
userStream.getAudioTracks()[0].enabled = this.sendAudio;
}
if (userStream.getVideoTracks()[0] != null) {
userStream.getVideoTracks()[0].enabled = this.sendVideo;
}
this.wrStream = userStream;
this.emitSrcEvent(this.wrStream);
callback(undefined, this);
})
.catch(error => {
this.accessIsDenied = true;
this.accessIsAllowed = false;
this.ee.emitEvent('access-denied-by-publisher');
console.error("Access denied", error);
callback(error, this);
});
}
private userMediaHasVideo(callback) {
navigator.mediaDevices.enumerateDevices().then(function (mediaDevices) {
var videoInput = mediaDevices.filter(function (deviceInfo) {
return deviceInfo.kind === 'videoinput';
})[0];
callback(videoInput != null);
});
}
publishVideoCallback(error, sdpOfferParam, wp) {
if (error) {
return console.error("(publish) SDP offer error: "
+ JSON.stringify(error));
}
console.log("Sending SDP offer to publish as "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("publishVideo", {
sdpOffer: sdpOfferParam,
doLoopback: this.displayMyRemote() || false
}, (error, response) => {
if (error) {
console.error("Error on publishVideo: " + JSON.stringify(error));
} else {
this.room.emitEvent('stream-published', [{
stream: this
}]);
this.processSdpAnswer(response.sdpAnswer);
}
});
}
startVideoCallback(error, sdpOfferParam, wp) {
if (error) {
return console.error("(subscribe) SDP offer error: "
+ JSON.stringify(error));
}
console.log("Sending SDP offer to subscribe to "
+ this.getId(), sdpOfferParam);
this.openVidu.sendRequest("receiveVideoFrom", {
sender: this.getId(),
sdpOffer: sdpOfferParam
}, (error, response) => {
if (error) {
console.error("Error on recvVideoFrom: " + JSON.stringify(error));
} else {
this.processSdpAnswer(response.sdpAnswer);
}
});
}
private initWebRtcPeer(sdpOfferCallback) {
if (this.local) {
let userMediaConstraints = {
audio: this.sendAudio,
video: this.sendVideo
}
let options: any = {
videoStream: this.wrStream,
mediaConstraints: userMediaConstraints,
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
}
if (this.dataChannel) {
options.dataChannelConfig = {
id: this.getChannelName(),
onopen: this.onDataChannelOpen,
onclose: this.onDataChannelClosed
};
options.dataChannels = true;
}
if (this.displayMyRemote()) {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendrecv(options, error => {
if (error) {
return console.error(error);
}
this.wp.generateOffer(sdpOfferCallback.bind(this));
});
} else {
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerSendonly(options, error => {
if (error) {
return console.error(error);
}
this.wp.generateOffer(sdpOfferCallback.bind(this));
});
}
} else {
let offerConstraints = {
mandatory: {
OfferToReceiveVideo: this.recvVideo,
OfferToReceiveAudio: this.recvAudio
}
};
console.log("Constraints of generate SDP offer (subscribing)",
offerConstraints);
let options = {
onicecandidate: this.connection.sendIceCandidate.bind(this.connection),
connectionConstraints: offerConstraints
}
this.wp = new kurentoUtils.WebRtcPeer.WebRtcPeerRecvonly(options, error => {
if (error) {
return console.error(error);
}
this.wp.generateOffer(sdpOfferCallback.bind(this));
});
}
console.log("Waiting for SDP offer to be generated ("
+ (this.local ? "local" : "remote") + " peer: " + this.getId() + ")");
}
publish() {
// FIXME: Throw error when stream is not local
if (this.isReady) {
this.initWebRtcPeer(this.publishVideoCallback);
} else {
this.ee.once('stream-ready', streamEvent => {
this.publish();
});
}
// FIXME: Now we have coupled connecting to a room and adding a
// stream to this room. But in the new API, there are two steps.
// This is the second step. For now, it do nothing.
}
subscribe() {
// FIXME: In the current implementation all participants are subscribed
// automatically to all other participants. We use this method only to
// negotiate SDP
this.initWebRtcPeer(this.startVideoCallback);
}
processSdpAnswer(sdpAnswer) {
let answer = new RTCSessionDescription({
type: 'answer',
sdp: sdpAnswer,
});
console.log(this.getId() + ": set peer connection with recvd SDP answer",
sdpAnswer);
let participantId = this.getId();
let pc = this.wp.peerConnection;
pc.setRemoteDescription(answer, () => {
// Avoids to subscribe to your own stream remotely
// except when showMyRemote is true
if (!this.local || this.displayMyRemote()) {
this.wrStream = pc.getRemoteStreams()[0];
console.log("Peer remote stream", this.wrStream);
if (this.wrStream != undefined) {
this.emitSrcEvent(this.wrStream);
this.speechEvent = kurentoUtils.WebRtcPeer.hark(this.wrStream, { threshold: this.room.thresholdSpeaker });
this.speechEvent.on('speaking', () => {
this.room.addParticipantSpeaking(participantId);
this.room.emitEvent('stream-speaking', [{
participantId: participantId
}]);
});
this.speechEvent.on('stopped_speaking', () => {
this.room.removeParticipantSpeaking(participantId);
this.room.emitEvent('stream-stopped-speaking', [{
participantId: participantId
}]);
});
}
for (let videoElement of this.videoElements) {
let thumbnailId = videoElement.thumb;
let video = videoElement.video;
video.src = URL.createObjectURL(this.wrStream);
video.onplay = () => {
console.log(this.getId() + ': ' + 'Video playing');
//show(thumbnailId);
//this.hideSpinner(this.getId());
};
}
this.room.emitEvent('stream-subscribed', [{
stream: this
}]);
}
}, error => {
console.error(this.getId() + ": Error setting SDP to the peer connection: "
+ JSON.stringify(error));
});
}
unpublish() {
if (this.wp) {
this.wp.dispose();
} else {
if (this.wrStream) {
this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop()
})
this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop()
})
}
}
if (this.speechEvent) {
this.speechEvent.stop();
}
console.log(this.getId() + ": Stream '" + this.id + "' unpublished");
}
dispose() {
function disposeElement(element) {
if (element && element.parentNode) {
element.parentNode.removeChild(element);
}
}
this.elements.forEach(e => disposeElement(e));
this.videoElements.forEach(ve => disposeElement(ve));
disposeElement("progress-" + this.getId());
if (this.wp) {
this.wp.dispose();
} else {
if (this.wrStream) {
this.wrStream.getAudioTracks().forEach(function (track) {
track.stop && track.stop()
})
this.wrStream.getVideoTracks().forEach(function (track) {
track.stop && track.stop()
})
}
}
if (this.speechEvent) {
this.speechEvent.stop();
}
console.log(this.getId() + ": Stream '" + this.id + "' disposed");
}
}

View File

@ -0,0 +1,3 @@
declare module "kurento-jsonrpc";
declare module "webrtc-adapter";
declare module "kurento-utils";

View File

@ -0,0 +1,4 @@
export * from './OpenViduInternal';
export * from './Connection';
export * from './SessionInternal';
export * from './Stream';

View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
"declaration": true,
"target": "es5",
"module": "commonjs",
//"noImplicitAny": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
//"allowUnusedLabels": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
//"allowUnreachableCode": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"strictNullChecks": true,
"outDir": "../../lib/OpenViduInternal",
"emitBOM": false,
"preserveConstEnums": true,
"sourceMap": true
},
//"buildOnSave": true,
"compileOnSave":true
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
openvidu-client/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target/

23
openvidu-client/.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>kurento-room-client-openvic</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/test/java=UTF-8
encoding/<project>=UTF-8

View File

@ -0,0 +1,5 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.8

View File

@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

119
openvidu-client/README.md Normal file
View File

@ -0,0 +1,119 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation badge](https://readthedocs.org/projects/fiware-orion/badge/?version=latest)](http://doc-kurento-room.readthedocs.org/en/latest/)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/fiware/stream-oriented-kurento/)
[![Support badge]( https://img.shields.io/badge/support-sof-yellowgreen.svg)](http://stackoverflow.com/questions/tagged/kurento)
[![][KurentoImage]][Kurento]
Copyright © 2013-2016 [Kurento]. Licensed under [Apache 2.0 License].
kurento-room-client
======================
Kurento Room Client contains the Java client-side of group
communications applications based on WebRTC.
It uses WebSockets and JSON-RPC to interact with the server-side
of the Room API.
What is Kurento
---------------
Kurento is an open source software project providing a platform suitable
for creating modular applications with advanced real-time communication
capabilities. For knowing more about Kurento, please visit the Kurento
project website: http://www.kurento.org.
Kurento is part of [FIWARE]. For further information on the relationship of
FIWARE and Kurento check the [Kurento FIWARE Catalog Entry]
Kurento is part of the [NUBOMEDIA] research initiative.
Documentation
-------------
The Kurento project provides detailed [documentation] including tutorials,
installation and development guides. A simplified version of the documentation
can be found on [readthedocs.org]. The [Open API specification] a.k.a. Kurento
Protocol is also available on [apiary.io].
Source
------
Code for other Kurento projects can be found in the [GitHub Kurento Group].
News and Website
----------------
Check the [Kurento blog]
Follow us on Twitter @[kurentoms].
Issue tracker
-------------
Issues and bug reports should be posted to the [GitHub Kurento bugtracker]
Licensing and distribution
--------------------------
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Contribution policy
-------------------
You can contribute to the Kurento community through bug-reports, bug-fixes, new
code or new documentation. For contributing to the Kurento community, drop a
post to the [Kurento Public Mailing List] providing full information about your
contribution and its value. In your contributions, you must comply with the
following guidelines
* You must specify the specific contents of your contribution either through a
detailed bug description, through a pull-request or through a patch.
* You must specify the licensing restrictions of the code you contribute.
* For newly created code to be incorporated in the Kurento code-base, you must
accept Kurento to own the code copyright, so that its open source nature is
guaranteed.
* You must justify appropriately the need and value of your contribution. The
Kurento project has no obligations in relation to accepting contributions
from third parties.
* The Kurento project leaders have the right of asking for further
explanations, tests or validations of any code contributed to the community
before it being incorporated into the Kurento code-base. You must be ready to
addressing all these kind of concerns before having your code approved.
Support
-------
The Kurento project provides community support through the [Kurento Public
Mailing List] and through [StackOverflow] using the tags *kurento* and
*fiware-kurento*.
Before asking for support, please read first the [Kurento Netiquette Guidelines]
[documentation]: http://www.kurento.org/documentation
[FIWARE]: http://www.fiware.org
[GitHub Kurento bugtracker]: https://github.com/Kurento/bugtracker/issues
[GitHub Kurento Group]: https://github.com/kurento
[kurentoms]: http://twitter.com/kurentoms
[Kurento]: http://kurento.org
[Kurento Blog]: http://www.kurento.org/blog
[Kurento FIWARE Catalog Entry]: http://catalogue.fiware.org/enablers/stream-oriented-kurento
[Kurento Netiquette Guidelines]: http://www.kurento.org/blog/kurento-netiquette-guidelines
[Kurento Public Mailing list]: https://groups.google.com/forum/#!forum/kurento
[KurentoImage]: https://secure.gravatar.com/avatar/21a2a12c56b2a91c8918d5779f1778bf?s=120
[Apache 2.0 License]: http://www.apache.org/licenses/LICENSE-2.0
[NUBOMEDIA]: http://www.nubomedia.eu
[StackOverflow]: http://stackoverflow.com/search?q=kurento
[Read-the-docs]: http://read-the-docs.readthedocs.org/
[readthedocs.org]: http://kurento.readthedocs.org/
[Open API specification]: http://kurento.github.io/doc-kurento/
[apiary.io]: http://docs.streamoriented.apiary.io/

104
openvidu-client/pom.xml Normal file
View File

@ -0,0 +1,104 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.openvidu</groupId>
<artifactId>openvidu</artifactId>
<version>1.0.0-beta.1</version>
</parent>
<artifactId>openvidu-client</artifactId>
<packaging>jar</packaging>
<name>OpenVidu Client</name>
<description>
OpenVidu client library for the client-side of OpenVidu Server
</description>
<url>https://github.com/OpenVidu/openvidu</url>
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<organization>
<name>OpenVidu</name>
<url>https://github.com/OpenVidu/openvidu</url>
</organization>
<scm>
<url>${openvidu.scm.url}</url>
<connection>scm:git:${openvidu.scm.connection}</connection>
<developerConnection>scm:git:${openvidu.scm.connection}</developerConnection>
<tag>develop</tag>
</scm>
<developers>
<developer>
<id>openvidu.io</id>
<name>-openvidu.io Community</name>
<organization>OpenVidu</organization>
<organizationUrl>http://openvidu.io</organizationUrl>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client</artifactId>
</dependency>
<!-- <dependency>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-server</artifactId>
</dependency>-->
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client-jetty</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>default</id>
<activation>
<property>
<name>default</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,223 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client;
import static io.openvidu.client.internal.ProtocolElements.CUSTOMREQUEST_METHOD;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_DATACHANNELS_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERID_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERSTREAMID_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERSTREAMS_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_ROOM_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_USER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.LEAVEROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_CANDIDATE_PARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_EPNAME_PARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_METHOD;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_SDPMIDPARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_SDPMLINEINDEX_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_DOLOOPBACK_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_SDPANSWER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_SDPOFFER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SENDER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_MESSAGE_PARAM;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_ROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_ROOM_PARAM;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_USER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.UNPUBLISHVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.UNSUBSCRIBEFROMVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.UNSUBSCRIBEFROMVIDEO_SENDER_PARAM;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.kurento.jsonrpc.client.JsonRpcClient;
import org.kurento.jsonrpc.client.JsonRpcClientWebSocket;
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.internal.JsonRoomUtils;
import io.openvidu.client.internal.Notification;
/**
* Java client for the room server.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class OpenViduClient {
private static final Logger log = LoggerFactory.getLogger(OpenViduClient.class);
private JsonRpcClient client;
private ServerJsonRpcHandler handler;
public OpenViduClient(String wsUri) {
this(new JsonRpcClientWebSocket(wsUri, new JsonRpcWSConnectionListener() {
@Override
public void reconnected(boolean sameServer) {
}
@Override
public void disconnected() {
log.warn("JsonRpcWebsocket connection: Disconnected");
}
@Override
public void connectionFailed() {
log.warn("JsonRpcWebsocket connection: Connection failed");
}
@Override
public void connected() {
}
@Override
public void reconnecting() {
log.warn("JsonRpcWebsocket connection: is reconnecting");
}
}, new SslContextFactory(true)));
}
public OpenViduClient(JsonRpcClient client) {
this.client = client;
this.handler = new ServerJsonRpcHandler();
this.client.setServerRequestHandler(this.handler);
}
public OpenViduClient(JsonRpcClient client, ServerJsonRpcHandler handler) {
this.client = client;
this.handler = handler;
this.client.setServerRequestHandler(this.handler);
}
public void close() throws IOException {
this.client.close();
}
public Map<String, List<String>> joinRoom(String roomName, String userName, Boolean dataChannels)
throws IOException {
JsonObject params = new JsonObject();
params.addProperty(JOINROOM_ROOM_PARAM, roomName);
params.addProperty(JOINROOM_USER_PARAM, userName);
if (dataChannels != null) {
params.addProperty(JOINROOM_DATACHANNELS_PARAM, dataChannels);
}
JsonElement result = client.sendRequest(JOINROOM_METHOD, params);
Map<String, List<String>> peers = new HashMap<String, List<String>>();
JsonArray jsonPeers = JsonRoomUtils.getResponseProperty(result, "value", JsonArray.class);
if (jsonPeers.size() > 0) {
Iterator<JsonElement> peerIt = jsonPeers.iterator();
while (peerIt.hasNext()) {
JsonElement peer = peerIt.next();
String peerId = JsonRoomUtils.getResponseProperty(peer, JOINROOM_PEERID_PARAM,
String.class);
List<String> streams = new ArrayList<String>();
JsonArray jsonStreams = JsonRoomUtils.getResponseProperty(peer, JOINROOM_PEERSTREAMS_PARAM,
JsonArray.class, true);
if (jsonStreams != null) {
Iterator<JsonElement> streamIt = jsonStreams.iterator();
while (streamIt.hasNext()) {
streams.add(JsonRoomUtils.getResponseProperty(streamIt.next(),
JOINROOM_PEERSTREAMID_PARAM, String.class));
}
}
peers.put(peerId, streams);
}
}
return peers;
}
public void leaveRoom() throws IOException {
client.sendRequest(LEAVEROOM_METHOD, new JsonObject());
}
public String publishVideo(String sdpOffer, boolean doLoopback) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(PUBLISHVIDEO_SDPOFFER_PARAM, sdpOffer);
params.addProperty(PUBLISHVIDEO_DOLOOPBACK_PARAM, doLoopback);
JsonElement result = client.sendRequest(PUBLISHVIDEO_METHOD, params);
return JsonRoomUtils.getResponseProperty(result, PUBLISHVIDEO_SDPANSWER_PARAM, String.class);
}
public void unpublishVideo() throws IOException {
client.sendRequest(UNPUBLISHVIDEO_METHOD, new JsonObject());
}
// sender should look like 'username_streamId'
public String receiveVideoFrom(String sender, String sdpOffer) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(RECEIVEVIDEO_SENDER_PARAM, sender);
params.addProperty(RECEIVEVIDEO_SDPOFFER_PARAM, sdpOffer);
JsonElement result = client.sendRequest(RECEIVEVIDEO_METHOD, params);
return JsonRoomUtils.getResponseProperty(result, RECEIVEVIDEO_SDPANSWER_PARAM, String.class);
}
// sender should look like 'username_streamId'
public void unsubscribeFromVideo(String sender) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(UNSUBSCRIBEFROMVIDEO_SENDER_PARAM, sender);
client.sendRequest(UNSUBSCRIBEFROMVIDEO_METHOD, params);
}
public void onIceCandidate(String endpointName, String candidate, String sdpMid,
int sdpMLineIndex) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(ONICECANDIDATE_EPNAME_PARAM, endpointName);
params.addProperty(ONICECANDIDATE_CANDIDATE_PARAM, candidate);
params.addProperty(ONICECANDIDATE_SDPMIDPARAM, sdpMid);
params.addProperty(ONICECANDIDATE_SDPMLINEINDEX_PARAM, sdpMLineIndex);
client.sendRequest(ONICECANDIDATE_METHOD, params);
}
public void sendMessage(String userName, String roomName, String message) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(SENDMESSAGE_USER_PARAM, userName);
params.addProperty(SENDMESSAGE_ROOM_PARAM, roomName);
params.addProperty(SENDMESSAGE_MESSAGE_PARAM, message);
client.sendRequest(SENDMESSAGE_ROOM_METHOD, params);
}
public JsonElement customRequest(JsonObject customReqParams) throws IOException {
return client.sendRequest(CUSTOMREQUEST_METHOD, customReqParams);
}
/**
* Polls the notifications list maintained by this client to obtain new events sent by server.
* This method blocks until there is a notification to return. This is a one-time operation for
* the returned element.
*
* @return a server notification object, null when interrupted while waiting
*/
public Notification getServerNotification() {
return this.handler.getNotification();
}
}

View File

@ -0,0 +1,77 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.openvidu.client;
public class OpenViduException extends RuntimeException {
private static final long serialVersionUID = 1L;
public static enum Code {
GENERIC_ERROR_CODE(999),
TRANSPORT_ERROR_CODE(803), TRANSPORT_RESPONSE_ERROR_CODE(802), TRANSPORT_REQUEST_ERROR_CODE(
801),
MEDIA_MUTE_ERROR_CODE(307), MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE(
306), MEDIA_RTP_ENDPOINT_ERROR_CODE(305), MEDIA_WEBRTC_ENDPOINT_ERROR_CODE(
304), MEDIA_ENDPOINT_ERROR_CODE(
303), MEDIA_SDP_ERROR_CODE(302), MEDIA_GENERIC_ERROR_CODE(301),
ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(
202), ROOM_GENERIC_ERROR_CODE(201),
USER_NOT_STREAMING_ERROR_CODE(105), EXISTING_USER_IN_ROOM_ERROR_CODE(
104), USER_CLOSED_ERROR_CODE(
103), USER_NOT_FOUND_ERROR_CODE(102), USER_GENERIC_ERROR_CODE(101),
USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402),
SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(403), TOKEN_CANNOT_BE_CREATED_ERROR_CODE(404),
USER_METADATA_FORMAT_INVALID_ERROR_CODE(500);
private int value;
private Code(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
private Code code = Code.GENERIC_ERROR_CODE;
public OpenViduException(Code code, String message) {
super(message);
this.code = code;
}
public Code getCode() {
return code;
}
public int getCodeValue() {
return code.getValue();
}
@Override
public String toString() {
return "Code: " + getCodeValue() + " " + super.toString();
}
}

View File

@ -0,0 +1,222 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.kurento.jsonrpc.DefaultJsonRpcHandler;
import org.kurento.jsonrpc.Transaction;
import org.kurento.jsonrpc.message.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.internal.IceCandidate;
import io.openvidu.client.internal.IceCandidateInfo;
import io.openvidu.client.internal.JsonRoomUtils;
import io.openvidu.client.internal.MediaErrorInfo;
import io.openvidu.client.internal.Notification;
import io.openvidu.client.internal.ParticipantEvictedInfo;
import io.openvidu.client.internal.ParticipantJoinedInfo;
import io.openvidu.client.internal.ParticipantLeftInfo;
import io.openvidu.client.internal.ParticipantPublishedInfo;
import io.openvidu.client.internal.ParticipantUnpublishedInfo;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.client.internal.RoomClosedInfo;
import io.openvidu.client.internal.SendMessageInfo;
/**
* Service that handles server JSON-RPC events.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ServerJsonRpcHandler extends DefaultJsonRpcHandler<JsonObject> {
private static final Logger log = LoggerFactory.getLogger(ServerJsonRpcHandler.class);
private BlockingQueue<Notification> notifications = new ArrayBlockingQueue<Notification>(100);
@Override
public void handleRequest(Transaction transaction, Request<JsonObject> request) throws Exception {
Notification notif = null;
try {
switch (request.getMethod()) {
case ProtocolElements.ICECANDIDATE_METHOD:
notif = iceCandidate(transaction, request);
break;
case ProtocolElements.MEDIAERROR_METHOD:
notif = mediaError(transaction, request);
break;
case ProtocolElements.PARTICIPANTJOINED_METHOD:
notif = participantJoined(transaction, request);
break;
case ProtocolElements.PARTICIPANTLEFT_METHOD:
notif = participantLeft(transaction, request);
break;
case ProtocolElements.PARTICIPANTEVICTED_METHOD:
notif = participantEvicted(transaction, request);
break;
case ProtocolElements.PARTICIPANTPUBLISHED_METHOD:
notif = participantPublished(transaction, request);
break;
case ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD:
notif = participantUnpublished(transaction, request);
break;
case ProtocolElements.ROOMCLOSED_METHOD:
notif = roomClosed(transaction, request);
break;
case ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD:
notif = participantSendMessage(transaction, request);
break;
default:
throw new Exception("Unrecognized request " + request.getMethod());
}
} catch (Exception e) {
log.error("Exception processing request {}", request, e);
transaction.sendError(e);
return;
}
if (notif != null) {
try {
notifications.put(notif);
log.debug("Enqueued notification {}", notif);
} catch (InterruptedException e) {
log.warn("Interrupted when enqueuing notification {}", notif, e);
}
}
}
private Notification participantSendMessage(Transaction transaction,
Request<JsonObject> request) {
String room = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_ROOM_PARAM, String.class);
String user = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_USER_PARAM, String.class);
String message = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_MESSAGE_PARAM, String.class);
SendMessageInfo eventInfo = new SendMessageInfo(room, user, message);
log.debug("Recvd send message event {}", eventInfo);
return eventInfo;
}
private Notification roomClosed(Transaction transaction, Request<JsonObject> request) {
String room = JsonRoomUtils.getRequestParam(request, ProtocolElements.ROOMCLOSED_ROOM_PARAM,
String.class);
RoomClosedInfo eventInfo = new RoomClosedInfo(room);
log.debug("Recvd room closed event {}", eventInfo);
return eventInfo;
}
private Notification participantUnpublished(Transaction transaction,
Request<JsonObject> request) {
String name = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTUNPUBLISHED_NAME_PARAM, String.class);
ParticipantUnpublishedInfo eventInfo = new ParticipantUnpublishedInfo(name);
log.debug("Recvd participant unpublished event {}", eventInfo);
return eventInfo;
}
private Notification participantPublished(Transaction transaction, Request<JsonObject> request) {
String id = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTPUBLISHED_USER_PARAM, String.class);
JsonArray jsonStreams = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTPUBLISHED_STREAMS_PARAM, JsonArray.class);
Iterator<JsonElement> streamIt = jsonStreams.iterator();
List<String> streams = new ArrayList<String>();
while (streamIt.hasNext()) {
streams.add(JsonRoomUtils.getResponseProperty(streamIt.next(),
ProtocolElements.PARTICIPANTPUBLISHED_STREAMID_PARAM, String.class));
}
ParticipantPublishedInfo eventInfo = new ParticipantPublishedInfo(id, streams);
log.debug("Recvd published event {}", eventInfo);
return eventInfo;
}
private Notification participantEvicted(Transaction transaction, Request<JsonObject> request) {
ParticipantEvictedInfo eventInfo = new ParticipantEvictedInfo();
log.debug("Recvd participant evicted event {}", eventInfo);
return eventInfo;
}
private Notification participantLeft(Transaction transaction, Request<JsonObject> request) {
String name = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTLEFT_NAME_PARAM, String.class);
ParticipantLeftInfo eventInfo = new ParticipantLeftInfo(name);
log.debug("Recvd participant left event {}", eventInfo);
return eventInfo;
}
private Notification participantJoined(Transaction transaction, Request<JsonObject> request) {
String id = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTJOINED_USER_PARAM, String.class);
ParticipantJoinedInfo eventInfo = new ParticipantJoinedInfo(id);
log.debug("Recvd participant joined event {}", eventInfo);
return eventInfo;
}
private Notification mediaError(Transaction transaction, Request<JsonObject> request) {
String description = JsonRoomUtils.getRequestParam(request,
ProtocolElements.MEDIAERROR_ERROR_PARAM, String.class);
MediaErrorInfo eventInfo = new MediaErrorInfo(description);
log.debug("Recvd media error event {}", eventInfo);
return eventInfo;
}
private Notification iceCandidate(Transaction transaction, Request<JsonObject> request) {
String candidate = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_CANDIDATE_PARAM, String.class);
String sdpMid = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_SDPMID_PARAM, String.class);
int sdpMLineIndex = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_SDPMLINEINDEX_PARAM, Integer.class);
IceCandidate iceCandidate = new IceCandidate(candidate, sdpMid, sdpMLineIndex);
String endpoint = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_EPNAME_PARAM, String.class);
IceCandidateInfo eventInfo = new IceCandidateInfo(iceCandidate, endpoint);
log.debug("Recvd ICE candidate event {}", eventInfo);
return eventInfo;
}
/**
* Blocks until an element is available and then returns it by removing it from the queue.
*
* @return a {@link Notification} from the queue, null when interrupted
* @see BlockingQueue#take()
*/
public Notification getNotification() {
try {
Notification notif = notifications.take();
log.debug("Dequeued notification {}", notif);
return notif;
} catch (InterruptedException e) {
log.info("Interrupted while polling notifications' queue");
return null;
}
}
}

View File

@ -0,0 +1,33 @@
package io.openvidu.client.internal;
public class IceCandidate {
private String candidate;
private String sdpMid;
private int sdpMLineIndex;
public IceCandidate(String candidate, String sdpMid, int sdpMLineIndex) {
super();
this.candidate = candidate;
this.sdpMid = sdpMid;
this.sdpMLineIndex = sdpMLineIndex;
}
public String getCandidate() {
return candidate;
}
public String getSdpMid() {
return sdpMid;
}
public int getSdpMLineIndex() {
return sdpMLineIndex;
}
@Override
public String toString() {
return "IceCandidate [candidate=" + candidate + ", sdpMid=" + sdpMid + ", sdpMLineIndex="
+ sdpMLineIndex + "]";
}
}

View File

@ -0,0 +1,70 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class IceCandidateInfo extends Notification {
private IceCandidate iceCandidate;
private String endpointName;
public IceCandidateInfo(IceCandidate iceCandidate, String endpointName) {
super(ProtocolElements.ICECANDIDATE_METHOD);
this.iceCandidate = iceCandidate;
this.endpointName = endpointName;
}
public IceCandidate getIceCandidate() {
return iceCandidate;
}
public void setIceCandidate(IceCandidate iceCandidate) {
this.iceCandidate = iceCandidate;
}
public String getEndpointName() {
return endpointName;
}
public void setEndpointName(String endpointName) {
this.endpointName = endpointName;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (endpointName != null) {
builder.append("endpointName=").append(endpointName).append(", ");
}
if (iceCandidate != null) {
builder.append("iceCandidate=[sdpMLineIndex= ").append(iceCandidate.getSdpMLineIndex())
.append(", sdpMid=").append(iceCandidate.getSdpMid()).append(", candidate=")
.append(iceCandidate.getCandidate()).append("]");
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,107 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
import org.kurento.jsonrpc.message.Request;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
/**
* JSON tools for extracting info from request or response elements.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class JsonRoomUtils {
public static <T> T getRequestParam(Request<JsonObject> request, String paramName, Class<T> type) {
return getRequestParam(request, paramName, type, false);
}
public static <T> T getRequestParam(Request<JsonObject> request, String paramName, Class<T> type,
boolean allowNull) {
JsonObject params = request.getParams();
if (params == null) {
if (!allowNull) {
throw new OpenViduException(Code.TRANSPORT_REQUEST_ERROR_CODE,
"Invalid request lacking parameter '" + paramName + "'");
} else {
return null;
}
}
return getConverted(params.get(paramName), paramName, type, allowNull);
}
public static <T> T getResponseProperty(JsonElement result, String property, Class<T> type) {
return getResponseProperty(result, property, type, false);
}
public static <T> T getResponseProperty(JsonElement result, String property, Class<T> type,
boolean allowNull) {
if (!(result instanceof JsonObject)) {
throw new OpenViduException(Code.TRANSPORT_RESPONSE_ERROR_CODE,
"Invalid response format. The response '" + result + "' should be a Json object");
}
return getConverted(result.getAsJsonObject().get(property), property, type, allowNull);
}
public static JsonArray getResponseArray(JsonElement result) {
if (!result.isJsonArray()) {
throw new OpenViduException(Code.TRANSPORT_RESPONSE_ERROR_CODE,
"Invalid response format. The response '" + result + "' should be a Json array");
}
return result.getAsJsonArray();
}
@SuppressWarnings("unchecked")
private static <T> T getConverted(JsonElement paramValue, String property, Class<T> type,
boolean allowNull) {
if (paramValue == null) {
if (allowNull) {
return null;
} else {
throw new OpenViduException(Code.TRANSPORT_ERROR_CODE, "Invalid method lacking parameter '"
+ property + "'");
}
}
if (type == String.class) {
if (paramValue.isJsonPrimitive()) {
return (T) paramValue.getAsString();
}
}
if (type == Integer.class) {
if (paramValue.isJsonPrimitive()) {
return (T) Integer.valueOf(paramValue.getAsInt());
}
}
if (type == JsonArray.class) {
if (paramValue.isJsonArray()) {
return (T) paramValue.getAsJsonArray();
}
}
throw new OpenViduException(Code.TRANSPORT_ERROR_CODE, "Param '" + property + "' with value '"
+ paramValue + "' is not a " + type.getName());
}
}

View File

@ -0,0 +1,55 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*
* @see Notification
*/
public class MediaErrorInfo extends Notification {
private String description;
public MediaErrorInfo(String description) {
super(ProtocolElements.MEDIAERROR_METHOD);
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (description != null) {
builder.append("description=").append(description);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,90 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* Wrapper for server events.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public abstract class Notification {
public enum Method {
ICECANDIDATE_METHOD(ProtocolElements.ICECANDIDATE_METHOD), MEDIAERROR_METHOD(
ProtocolElements.MEDIAERROR_METHOD), PARTICIPANTJOINED_METHOD(
ProtocolElements.PARTICIPANTJOINED_METHOD), PARTICIPANTLEFT_METHOD(
ProtocolElements.PARTICIPANTLEFT_METHOD), PARTICIPANTEVICTED_METHOD(
ProtocolElements.PARTICIPANTEVICTED_METHOD), PARTICIPANTPUBLISHED_METHOD(
ProtocolElements.PARTICIPANTPUBLISHED_METHOD), PARTICIPANTUNPUBLISHED_METHOD(
ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD), ROOMCLOSED_METHOD(
ProtocolElements.ROOMCLOSED_METHOD), PARTICIPANTSENDMESSAGE_METHOD(
ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD);
private String methodValue;
private Method(String val) {
this.methodValue = val;
}
public String getMethodValue() {
return methodValue;
}
public static Method getFromValue(String val) {
for (Method m : Method.values()) {
if (m.methodValue.equals(val)) {
return m;
}
}
return null;
}
@Override
public String toString() {
return getMethodValue().toString();
}
}
private Method method;
public Notification(Method method) {
this.setMethod(method);
}
public Notification(String methodValue) {
this(Method.getFromValue(methodValue));
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (method != null) {
builder.append("method=").append(method);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,30 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantEvictedInfo extends Notification {
public ParticipantEvictedInfo() {
super(ProtocolElements.PARTICIPANTEVICTED_METHOD);
}
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantJoinedInfo extends Notification {
private String id;
public ParticipantJoinedInfo(String id) {
super(ProtocolElements.PARTICIPANTJOINED_METHOD);
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (id != null) {
builder.append("id=").append(id);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantLeftInfo extends Notification {
private String name;
public ParticipantLeftInfo(String name) {
super(ProtocolElements.PARTICIPANTLEFT_METHOD);
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (name != null) {
builder.append("name=").append(name);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,69 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
import java.util.List;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantPublishedInfo extends Notification {
private String id;
private List<String> streams;
public ParticipantPublishedInfo(String id, List<String> streams) {
super(ProtocolElements.PARTICIPANTPUBLISHED_METHOD);
this.id = id;
this.streams = streams;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public List<String> getStreams() {
return streams;
}
public void setStreams(List<String> streams) {
this.streams = streams;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (id != null) {
builder.append("id=").append(id).append(", ");
}
if (streams != null) {
builder.append("streams=").append(streams);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantUnpublishedInfo extends Notification {
private String name;
public ParticipantUnpublishedInfo(String name) {
super(ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD);
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (name != null) {
builder.append("name=").append(name);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,105 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* This class defines constant values of client-server messages and their parameters.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ProtocolElements {
// ---------------------------- CLIENT REQUESTS -----------------------
public static final String SENDMESSAGE_ROOM_METHOD = "sendMessage";
public static final String SENDMESSAGE_USER_PARAM = "userMessage";
public static final String SENDMESSAGE_ROOM_PARAM = "roomMessage";
public static final String SENDMESSAGE_MESSAGE_PARAM = "message";
public static final String LEAVEROOM_METHOD = "leaveRoom";
public static final String JOINROOM_METHOD = "joinRoom";
public static final String JOINROOM_USER_PARAM = "user";
public static final String JOINROOM_TOKEN_PARAM = "token";
public static final String JOINROOM_ROOM_PARAM = "session";
public static final String JOINROOM_METADATA_PARAM = "metadata";
public static final String JOINROOM_DATACHANNELS_PARAM = "dataChannels";
public static final String JOINROOM_PEERID_PARAM = "id";
public static final String JOINROOM_PEERSTREAMS_PARAM = "streams";
public static final String JOINROOM_PEERSTREAMID_PARAM = "id";
public static final String PUBLISHVIDEO_METHOD = "publishVideo";
public static final String PUBLISHVIDEO_SDPOFFER_PARAM = "sdpOffer";
public static final String PUBLISHVIDEO_DOLOOPBACK_PARAM = "doLoopback";
public static final String PUBLISHVIDEO_SDPANSWER_PARAM = "sdpAnswer";
public static final String UNPUBLISHVIDEO_METHOD = "unpublishVideo";
public static final String RECEIVEVIDEO_METHOD = "receiveVideoFrom";
public static final String RECEIVEVIDEO_SDPOFFER_PARAM = "sdpOffer";
public static final String RECEIVEVIDEO_SENDER_PARAM = "sender";
public static final String RECEIVEVIDEO_SDPANSWER_PARAM = "sdpAnswer";
public static final String UNSUBSCRIBEFROMVIDEO_METHOD = "unsubscribeFromVideo";
public static final String UNSUBSCRIBEFROMVIDEO_SENDER_PARAM = "sender";
public static final String ONICECANDIDATE_METHOD = "onIceCandidate";
public static final String ONICECANDIDATE_EPNAME_PARAM = "endpointName";
public static final String ONICECANDIDATE_CANDIDATE_PARAM = "candidate";
public static final String ONICECANDIDATE_SDPMIDPARAM = "sdpMid";
public static final String ONICECANDIDATE_SDPMLINEINDEX_PARAM = "sdpMLineIndex";
public static final String CUSTOMREQUEST_METHOD = "customRequest";
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
public static final String PARTICIPANTJOINED_METHOD = "participantJoined";
public static final String PARTICIPANTJOINED_USER_PARAM = "id";
public static final String PARTICIPANTJOINED_METADATA_PARAM = "metadata";
public static final String PARTICIPANTLEFT_METHOD = "participantLeft";
public static final String PARTICIPANTLEFT_NAME_PARAM = "name";
public static final String PARTICIPANTEVICTED_METHOD = "participantEvicted";
public static final String PARTICIPANTPUBLISHED_METHOD = "participantPublished";
public static final String PARTICIPANTPUBLISHED_USER_PARAM = "id";
public static final String PARTICIPANTPUBLISHED_STREAMS_PARAM = "streams";
public static final String PARTICIPANTPUBLISHED_STREAMID_PARAM = "id";
public static final String PARTICIPANTUNPUBLISHED_METHOD = "participantUnpublished";
public static final String PARTICIPANTUNPUBLISHED_NAME_PARAM = "name";
public static final String PARTICIPANTSENDMESSAGE_METHOD = "sendMessage";
public static final String PARTICIPANTSENDMESSAGE_USER_PARAM = "user";
public static final String PARTICIPANTSENDMESSAGE_ROOM_PARAM = "room";
public static final String PARTICIPANTSENDMESSAGE_MESSAGE_PARAM = "message";
public static final String ROOMCLOSED_METHOD = "roomClosed";
public static final String ROOMCLOSED_ROOM_PARAM = "room";
public static final String MEDIAERROR_METHOD = "mediaError";
public static final String MEDIAERROR_ERROR_PARAM = "error";
public static final String ICECANDIDATE_METHOD = "iceCandidate";
public static final String ICECANDIDATE_EPNAME_PARAM = "endpointName";
public static final String ICECANDIDATE_CANDIDATE_PARAM = "candidate";
public static final String ICECANDIDATE_SDPMID_PARAM = "sdpMid";
public static final String ICECANDIDATE_SDPMLINEINDEX_PARAM = "sdpMLineIndex";
public static final String CUSTOM_NOTIFICATION = "custonNotification";
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class RoomClosedInfo extends Notification {
private String room;
public RoomClosedInfo(String room) {
super(ProtocolElements.ROOMCLOSED_METHOD);
this.room = room;
}
public String getRoom() {
return room;
}
public void setRoom(String room) {
this.room = room;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (room != null) {
builder.append("room=").append(room);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,80 @@
/*
* (C) Copyright 2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class SendMessageInfo extends Notification {
private String room;
private String user;
private String message;
public SendMessageInfo(String room, String user, String message) {
super(ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD);
this.room = room;
this.user = user;
this.message = message;
}
public String getRoom() {
return room;
}
public void setRoom(String room) {
this.room = room;
}
public String getUser() {
return user;
}
public void setUser(String user) {
this.user = user;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (room != null) {
builder.append("room=").append(room).append(", ");
}
if (user != null) {
builder.append("user=").append(user).append(", ");
}
if (message != null) {
builder.append("message=").append(message);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -0,0 +1,74 @@
/*
* (C) Copyright 2016 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.test;
import static io.openvidu.client.internal.ProtocolElements.*;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.kurento.jsonrpc.client.JsonRpcClient;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import io.openvidu.client.OpenViduClient;
import io.openvidu.client.ServerJsonRpcHandler;
/**
* Unit tests for the room client protocol.
*
* @author Radu Tom Vlad (rvlad@naevatec.com)
* @since 6.3.1
*/
public class OpenViduClientTest {
private OpenViduClient client;
private ServerJsonRpcHandler serverHandler;
private JsonRpcClient jsonRpcClient;
@Before
public void setup() {
jsonRpcClient = mock(JsonRpcClient.class);
serverHandler = new ServerJsonRpcHandler();
client = new OpenViduClient(jsonRpcClient, serverHandler);
}
@Test
public void testRoomJoin() throws IOException {
JsonObject params = new JsonObject();
params.addProperty(JOINROOM_ROOM_PARAM, "room");
params.addProperty(JOINROOM_USER_PARAM, "user");
JsonObject result = new JsonObject();
JsonArray value = new JsonArray();
result.add("value", value);
Map<String, List<String>> joinResult = new HashMap<String, List<String>>();
when(jsonRpcClient.sendRequest(JOINROOM_METHOD, params)).thenReturn(result);
assertThat(client.joinRoom("room", "user", null), is(joinResult));
}
}

View File

@ -1,13 +0,0 @@
# Output of 'npm pack'
*.tgz
**/node_modules/
**/.angular/
**/.vscode
node_modules
dist/
docs/
coverage/**

View File

@ -1,5 +0,0 @@
build
node_modules
.github
dist
docs

View File

@ -1,10 +0,0 @@
{
"singleQuote": true,
"printWidth": 140,
"trailingComma": "none",
"semi": true,
"bracketSpacing": true,
"useTabs": true,
"jsxSingleQuote": true,
"tabWidth": 4
}

View File

@ -1,43 +0,0 @@
# Openvidu Angular TestAPP
## Architechture
```
openvidu-components-angular
└─── src (openvidu-components-testapp)
└───projects
└─── openvidu-components-angular
```
## How to develop with ease:
Run `ng serve` for a dev server.
Run, in a new terminal, `npm run lib:serve` for serving the openvidu-components-angular library with live reload for listening changes
## Code scaffolding
For generate new components in openvidu-components-angular:
```bash
ng g component components/component-name --project=openvidu-components-angular
```
## Build library
```bash
npm run lib:build
```
## Publishing
After the library is built, tun the following command:
```bash
cd dist/ && npm publish
```

View File

@ -1,157 +0,0 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"openvidu-components-testapp": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:application",
"options": {
"outputPath": {
"base": "dist/openvidu-components-testapp",
"browser": ""
},
"index": "src/index.html",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": [],
"browser": "src/main.ts"
},
"configurations": {
"development": {
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"extractLicenses": true
},
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"proxyConfig": "src/proxy.conf.json",
"buildTarget": "openvidu-components-testapp:build"
},
"configurations": {
"development": {
"buildTarget": "openvidu-components-testapp:build:development"
},
"production": {
"buildTarget": "openvidu-components-testapp:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "openvidu-components-testapp:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": ["zone.js"],
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": ["src/favicon.ico", "src/assets"],
"styles": ["src/styles.scss"],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["tsconfig.app.json", "tsconfig.spec.json", "e2e/tsconfig.json"],
"exclude": ["**/node_modules/**"]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "openvidu-components-testapp:serve"
},
"configurations": {
"production": {
"devServerTarget": "openvidu-components-testapp:serve:production"
}
}
}
}
},
"openvidu-components-angular": {
"projectType": "library",
"root": "projects/openvidu-components-angular",
"sourceRoot": "projects/openvidu-components-angular/src",
"prefix": "ov",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:ng-packagr",
"options": {
"project": "projects/openvidu-components-angular/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "projects/openvidu-components-angular/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "projects/openvidu-components-angular/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "projects/openvidu-components-angular/src/test.ts",
"tsConfig": "projects/openvidu-components-angular/tsconfig.spec.json",
"karmaConfig": "projects/openvidu-components-angular/karma.conf.js"
}
}
}
}
},
"cli": {
"analytics": false
}
}

View File

@ -1,621 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
let url = '';
describe('Testing API Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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);
url = `${TestAppConfig.appUrl}&roomName=API_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
if (await utils.isPresent('#session-container')) {
await utils.leaveRoom();
}
} catch (error) {}
await browser.sleep(500);
await browser.quit();
});
it('should set the MINIMAL UI', async () => {
await browser.get(`${url}&minimal=true`);
// Checking if prejoin page exist
await utils.checkPrejoinIsPresent();
// Checking if audio detection is not displayed
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
const joinButton = await utils.waitForElement('#join-button');
await joinButton.click();
// Checking if session container is present
await utils.checkSessionIsPresent();
// Checking if layout is present
await utils.checkLayoutPresent();
// Checking if stream component is present
utils.checkStreamIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeFalse();
// Checking if more options button is not present
expect(await utils.isPresent('#more-options-btn')).toBeFalse();
// Checking if participants panel button is not present
expect(await utils.isPresent('#participants-panel-btn')).toBeFalse();
// Checking if activities panel button is not present
expect(await utils.isPresent('#activities-panel-btn')).toBeFalse();
// Checking if logo is not displayed
expect(await utils.isPresent('#branding-logo')).toBeFalse();
// Checking if session name is not displayed
expect(await utils.isPresent('#session-name')).toBeFalse();
// Checking if nickname is not displayed
expect(await utils.getNumberOfElements('#participant-name-container')).toEqual(0);
// Checking if audio detection is not displayed
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
// Checking if settings button is not displayed
expect(await utils.isPresent('#settings-container')).toBeFalse();
});
it('should change the UI LANG in prejoin page', async () => {
await browser.get(`${url}&lang=es`);
await utils.checkPrejoinIsPresent();
await utils.waitForElement('.language-selector');
const element = await utils.waitForElement('#join-button');
expect(await element.getText()).toEqual('Unirme ahora');
});
it('should change the UI LANG in room page', async () => {
await browser.get(`${url}&prejoin=false&lang=es`);
await utils.checkLayoutPresent();
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('#default-settings-panel')).toBeTrue();
const panelTitle = await utils.waitForElement('.panel-title');
expect(await panelTitle.getText()).toEqual('Configuración');
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('.language-selector');
await utils.clickOn('.language-selector');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.language-option')).toEqual(2);
await utils.clickOn('.language-option');
await browser.sleep(500);
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await browser.sleep(500);
await utils.waitForElement('#settings-container');
await utils.waitForElement('.full-lang-button');
await utils.clickOn('.full-lang-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.language-option')).toEqual(2);
});
it('should show the PREJOIN page', async () => {
await browser.get(`${url}&prejoin=true`);
await utils.checkPrejoinIsPresent();
});
it('should not show the PREJOIN page', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
});
it('should join to Room', async () => {
await browser.get(`${url}`);
// Checking if prejoin page exist
await utils.checkPrejoinIsPresent();
const joinButton = await utils.waitForElement('#join-button');
await joinButton.click();
// Checking if session container is present
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
});
it('should show the token error WITH prejoin page', async () => {
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TEST_TOKEN&participantName=PNAME`;
await browser.get(`${fixedUrl}`);
// Checking if prejoin page exist
await utils.checkPrejoinIsPresent();
await utils.waitForElement('#join-button');
await utils.clickOn('#join-button');
// Checking if session container is present
await utils.checkSessionIsPresent();
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
// Go to first tab
const tabs = await browser.getAllWindowHandles();
browser.switchTo().window(tabs[1]);
await utils.checkPrejoinIsPresent();
await utils.waitForElement('#join-button');
await utils.clickOn('#join-button');
// Checking if token error is displayed
await utils.waitForElement('#token-error');
expect(await utils.isPresent('#token-error')).toBeTrue();
});
it('should show the token error WITHOUT prejoin page', async () => {
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TOKEN_ERROR&prejoin=false&participantName=PNAME`;
await browser.get(`${fixedUrl}`);
// Checking if session container is present
await utils.checkSessionIsPresent();
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
// Go to first tab
const tabs = await browser.getAllWindowHandles();
browser.switchTo().window(tabs[1]);
// Checking if token error is displayed
await utils.waitForElement('#openvidu-dialog');
expect(await utils.isPresent('#openvidu-dialog')).toBeTrue();
});
it('should run the app with VIDEO DISABLED in prejoin page', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false`);
await utils.checkPrejoinIsPresent();
// Checking if video is displayed
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
await utils.waitForElement('#videocam_off');
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).toBeTrue();
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
});
it('should run the app with VIDEO DISABLED and WITHOUT PREJOIN page', async () => {
await browser.get(`${url}&prejoin=false&videoEnabled=false`);
await utils.checkSessionIsPresent();
await utils.checkLayoutPresent();
// Checking if video is displayed
await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('#video-poster')).toEqual(1);
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).toBeTrue();
});
it('should run the app with AUDIO DISABLED in prejoin page', async () => {
await browser.get(`${url}&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
// Checking if video is displayed
await utils.checkVideoElementIsPresent();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
await utils.clickOn('#join-button');
await utils.checkSessionIsPresent();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
});
it('should run the app with AUDIO DISABLED and WITHOUT PREJOIN page', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
await browser.sleep(1000);
await utils.checkSessionIsPresent();
// Checking if video is displayed
await utils.checkVideoElementIsPresent();
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.waitForElement('#mic_off');
expect(await utils.isPresent('#mic_off')).toBeTrue();
});
it('should run the app without camera button', async () => {
await browser.get(`${url}&prejoin=false&cameraBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if camera button is not present
expect(await utils.isPresent('#camera-btn')).toBeFalse();
});
it('should run the app without microphone button', async () => {
await browser.get(`${url}&prejoin=false&microphoneBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if microphone button is not present
expect(await utils.isPresent('#microphone-btn')).toBeFalse();
});
it('should HIDE the SCREENSHARE button', async () => {
await browser.get(`${url}&prejoin=false&screenshareBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if screenshare button is not present
expect(await utils.isPresent('#screenshare-btn')).toBeFalse();
});
it('should HIDE the FULLSCREEN button', async () => {
await browser.get(`${url}&prejoin=false&fullscreenBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.getNumberOfElements('#fullscreen-btn')).toEqual(0);
});
xit('should HIDE the CAPTIONS button', async () => {
await browser.get(`${url}&prejoin=false&toolbarCaptionsBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.toggleToolbarMoreOptions();
// Checking if captions button is not present
expect(await utils.isPresent('#captions-btn')).toBeFalse();
await utils.clickOn('#toolbar-settings-btn');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
expect(await utils.isPresent('#captions-opt')).toBeFalse();
});
it('should HIDE the TOOLBAR RECORDING button', async () => {
await browser.get(`${url}&prejoin=false&toolbarRecordingButton=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.toggleToolbarMoreOptions();
// Checking if recording button is not present
expect(await utils.isPresent('#recording-btn')).toBeFalse();
});
it('should HIDE the TOOLBAR BROADCASTING button', async () => {
await browser.get(`${url}&prejoin=false&toolbarBroadcastingButton=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.toggleToolbarMoreOptions();
// Checking if broadcasting button is not present
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
});
it('should HIDE the TOOLBAR SETTINGS button', async () => {
await browser.get(`${url}&prejoin=false&toolbarSettingsBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#toolbar-settings-btn')).toBeFalse();
});
it('should HIDE the LEAVE button', async () => {
await browser.get(`${url}&prejoin=false&leaveBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if leave button is not present
expect(await utils.getNumberOfElements('#leave-btn')).toEqual(0);
});
it('should HIDE the ACTIVITIES PANEL button', async () => {
await browser.get(`${url}&prejoin=false&activitiesPanelBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if activities panel button is not present
expect(await utils.isPresent('#activities-panel-btn')).toBeFalse();
});
it('should HIDE the CHAT PANEL button', async () => {
await browser.get(`${url}&prejoin=false&chatPanelBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if chat panel button is not present
expect(await utils.isPresent('#chat-panel-btn')).toBeFalse();
});
it('should HIDE the PARTICIPANTS PANEL button', async () => {
await browser.get(`${url}&prejoin=false&participantsPanelBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if participants panel button is not present
expect(await utils.isPresent('#participants-panel-btn')).toBeFalse();
});
it('should HIDE the LOGO', async () => {
await browser.get(`${url}&prejoin=false&displayLogo=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if toolbar is present
await utils.waitForElement('#info-container');
expect(await utils.isPresent('#info-container')).toBeTrue();
// Checking if logo is not displayed
expect(await utils.isPresent('#branding-logo')).toBeFalse();
});
it('should HIDE the ROOM NAME', async () => {
await browser.get(`${url}&prejoin=false&displayRoomName=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if toolbar is present
await utils.waitForElement('#info-container');
expect(await utils.isPresent('#info-container')).toBeTrue();
// Checking if session name is not displayed
expect(await utils.isPresent('#session-name')).toBeFalse();
});
it('should HIDE the PARTICIPANT NAME', async () => {
await browser.get(`${url}&prejoin=false&displayParticipantName=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if stream component is present
await utils.checkStreamIsPresent();
// Checking if nickname is not present
expect(await utils.isPresent('#participant-name-container')).toBeFalse();
});
it('should HIDE the AUDIO DETECTION element', async () => {
await browser.get(`${url}&prejoin=false&displayAudioDetection=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if stream component is present
await utils.checkStreamIsPresent();
// Checking if audio detection is not present
expect(await utils.isPresent('#audio-wave-container')).toBeFalse();
});
it('should HIDE the STREAM VIDEO CONTROLS button', async () => {
await browser.get(`${url}&prejoin=false&videoControls=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if stream component is present
await utils.checkStreamIsPresent();
// Checking if settings button is not present
expect(await utils.isPresent('.stream-video-controls')).toBeFalse();
});
it('should HIDE the MUTE button in participants panel', async () => {
const roomName = 'e2etest';
const fixedUrl = `${TestAppConfig.appUrl}&prejoin=false&participantMuteBtn=false&roomName=${roomName}`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
const participantsButton = await utils.waitForElement('#participants-panel-btn');
await participantsButton.click();
// Checking if participatns panel is displayed
await utils.waitForElement('#participants-container');
expect(await utils.isPresent('#participants-container')).toBeTrue();
// Checking remote participants item
expect(await utils.isPresent('#remote-participant-item')).toBeFalse();
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedUrl}&participantName=SecondParticipant")`;
await browser.executeScript(newTabScript);
await browser.sleep(10000);
// Go to first tab
const tabs = await browser.getAllWindowHandles();
browser.switchTo().window(tabs[0]);
// Checking if mute button is not displayed in participant item
await utils.waitForElement('#remote-participant-item');
expect(await utils.isPresent('#remote-participant-item')).toBeTrue();
expect(await utils.isPresent('#mute-btn')).toBeFalse();
});
it('should HIDE the RECORDING ACTIVITY in activities panel', async () => {
let element;
const fixedUrl = `${url}&prejoin=false&activitiesPanelRecordingActivity=false`;
await browser.get(fixedUrl);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
element = await utils.waitForElement('#activities-panel-btn');
await element.click();
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).toBeTrue();
// await browser.sleep(1000);
// Checking if recording activity exists
await utils.waitForElement('.activities-body-container');
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
});
it('should HIDE the BROADCASTING ACTIVITY in activities panel', async () => {
await browser.get(`${url}&prejoin=false&activitiesPanelBroadcastingActivity=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
await utils.waitForElement('#activities-panel-btn');
await utils.clickOn('#activities-panel-btn');
// Checking if participatns panel is displayed
await utils.waitForElement('#default-activities-panel');
expect(await utils.isPresent('#default-activities-panel')).toBeTrue();
// await browser.sleep(1000);
// Checking if recording activity exists
await utils.waitForElement('.activities-body-container');
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
});
});

View File

@ -1,179 +0,0 @@
import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
//TODO: Uncomment when captions are implemented
// describe('Testing captions features', () => {
// let browser: WebDriver;
// let utils: OpenViduComponentsPO;
// async function createChromeBrowser(): Promise<WebDriver> {
// 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 () => {
// await browser.quit();
// });
// it('should OPEN the CAPTIONS container', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// });
// it('should OPEN the SETTINGS panel from captions button', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// await utils.waitForElement('#caption-settings-btn');
// await utils.clickOn('#caption-settings-btn');
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// await utils.waitForElement('ov-captions-settings');
// // Expect caption button is not present
// expect(await utils.isPresent('#caption-settings-btn')).toBeFalse();
// });
// it('should TOGGLE the CAPTIONS container from settings panel', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// await utils.waitForElement('#caption-settings-btn');
// await utils.clickOn('#caption-settings-btn');
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// await utils.clickOn('#captions-toggle-slide');
// expect(await utils.isPresent('.captions-container')).toBeFalse();
// await browser.sleep(200);
// await utils.clickOn('#captions-toggle-slide');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// });
// it('should change the CAPTIONS language', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// // Checking if toolbar is present
// await utils.checkToolbarIsPresent();
// // Open more options menu
// await utils.clickOn('#more-options-btn');
// await browser.sleep(500);
// // Checking if button panel is present
// await utils.waitForElement('#more-options-menu');
// expect(await utils.isPresent('#more-options-menu')).toBeTrue();
// // Checking if captions button is present
// await utils.waitForElement('#captions-btn');
// expect(await utils.isPresent('#captions-btn')).toBeTrue();
// await utils.clickOn('#captions-btn');
// await utils.waitForElement('.captions-container');
// await utils.waitForElement('#caption-settings-btn');
// await utils.clickOn('#caption-settings-btn');
// await browser.sleep(500);
// await utils.waitForElement('.settings-container');
// expect(await utils.isPresent('.settings-container')).toBeTrue();
// await utils.waitForElement('ov-captions-settings');
// expect(await utils.isPresent('.captions-container')).toBeTrue();
// await utils.clickOn('.lang-button');
// await browser.sleep(500);
// await utils.clickOn('#es-ES');
// await utils.clickOn('.panel-close-button');
// const button = await utils.waitForElement('#caption-settings-btn');
// expect(await button.getText()).toEqual('settingsEspañol');
// });
// });

View File

@ -1,120 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Testing CHAT features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 send messages', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.togglePanel('chat');
await browser.sleep(500);
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('Test message');
await utils.clickOn('#send-btn');
await utils.waitForElement('.message');
await utils.getNumberOfElements('.message');
expect(await utils.isPresent('.message')).toBeTrue();
expect(await utils.getNumberOfElements('.message')).toEqual(1);
await input.sendKeys('Test message');
await utils.clickOn('#send-btn');
expect(await utils.getNumberOfElements('.message')).toEqual(2);
});
it('should receive a message', async () => {
const roomName = 'chattingE2E';
let pName = `participant${Math.floor(Math.random() * 1000)}`;
const fixedUrl = `${url}&prejoin=false&roomName=${roomName}`;
await browser.get(fixedUrl);
await browser.sleep(1000);
await utils.checkLayoutPresent();
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedUrl}&participantName=${pName}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.togglePanel('chat');
await browser.sleep(1000);
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('test message');
await utils.clickOn('#send-btn');
// Go to first tab
browser.switchTo().window(tabs[0]);
await utils.waitForElement('.snackbarNotification');
await utils.togglePanel('chat');
await browser.sleep(1000);
await utils.waitForElement('.message');
const participantName = await utils.waitForElement('.participant-name-container>p');
expect(await utils.getNumberOfElements('.message')).toEqual(1);
expect(await participantName.getText()).toEqual(pName);
});
it('should send an url message and converts in a link', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.togglePanel('chat');
await browser.sleep(500);
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
const input = await utils.waitForElement('#chat-input');
await input.sendKeys('demos.openvidu.io');
await utils.clickOn('#send-btn');
await utils.waitForElement('.chat-message a');
expect(await utils.isPresent('.chat-message a')).toBeTrue();
});
});

View File

@ -1,3 +0,0 @@
export const LAUNCH_MODE = process.env.LAUNCH_MODE || 'DEV';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;

View File

@ -1,604 +0,0 @@
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<WebDriver> {
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 onVideoDeviceChanged event on prejoin', async () => {
await browser.get(`${url}&fakeDevices=true`);
await utils.checkPrejoinIsPresent();
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');
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-dropdown');
await utils.clickOn('#video-dropdown');
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 onAudioDeviceChanged event on prejoin', async () => {
await browser.get(`${url}&fakeDevices=true`);
await utils.checkPrejoinIsPresent();
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');
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-dropdown');
await utils.clickOn('#audio-dropdown');
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('.language-selector');
await utils.clickOn('.language-selector');
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('.full-lang-button');
await utils.clickOn('.full-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 and onRecordingStopRequested 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();
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`);
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 and onRecordingStopRequested 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 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();
});
// PARTICIPANT 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&redirectToHome=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();
await utils.waitForElement('#events');
// 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();
// });
});

View File

@ -1,84 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { OpenViduComponentsPO } from './utils.po.test';
import { TestAppConfig } from './selenium.conf';
let url = '';
describe('Testing Internal Directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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);
url = `${TestAppConfig.appUrl}&roomName=INTERNAL_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
});
afterEach(async () => {
try {
} catch (error) {}
await browser.sleep(500);
await browser.quit();
});
it('should show/hide toolbar view recording button with toolbarViewRecordingsButton directive', async () => {
await browser.get(`${url}&prejoin=false&toolbarViewRecordingsButton=true`);
await utils.checkSessionIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.toggleToolbarMoreOptions();
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
});
it('should show/hide participant name in prejoin with prejoinDisplayParticipantName directive', async () => {
await browser.get(`${url}&prejoin=true`);
await utils.checkPrejoinIsPresent();
expect(await utils.isPresent('.participant-name-container')).toBeTrue();
await browser.get(`${url}&prejoin=true&prejoinDisplayParticipantName=false`);
await browser.navigate().refresh();
await utils.checkPrejoinIsPresent();
expect(await utils.isPresent('.participant-name-container')).toBeFalse();
});
it('should show/hide view recordings button with recordingActivityViewRecordingsButton directive', async () => {
await browser.get(`${url}&prejoin=false&recordingActivityViewRecordingsButton=true`);
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
});
it('should show/hide start/stop recording buttons with recordingActivityStartStopRecordingButton directive', async () => {
await browser.get(`${url}&prejoin=false&recordingActivityStartStopRecordingButton=false`);
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#start-recording-btn')).toBeFalse();
await browser.sleep(3000);
await browser.get(`${url}&prejoin=false`);
await browser.navigate().refresh();
await utils.checkSessionIsPresent();
await utils.togglePanel('activities');
await utils.clickOn('#recording-activity');
expect(await utils.isPresent('#start-recording-btn')).toBeTrue();
});
});

View File

@ -1,177 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { getBrowserOptionsWithoutDevices, TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Media Devices: Virtual Device Replacement and Permissions Handling', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should allow selecting and replacing the video track with a custom virtual device in the prejoin page', async () => {
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&fakeDevices=true`);
let videoDevices = await utils.waitForElement('#video-dropdown');
await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click();
let videoLabel;
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1');
await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0');
await element.click();
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0');
});
it('should allow selecting and replacing the video track with a custom virtual device in the videoconference page', async () => {
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
await browser.sleep(500);
await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
let videoDevices = await utils.waitForElement('#video-dropdown');
await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click();
let videoLabel;
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1');
await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0');
await element.click();
await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0');
});
it('should replace the screen track with a custom virtual device', async () => {
const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
await utils.checkLayoutPresent();
await utils.checkToolbarIsPresent();
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
let screenLabel = await browser.executeScript<string>(script);
expect(screenLabel).not.toEqual('custom_fake_screen');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#replace-screen-button');
await utils.clickOn('#replace-screen-button');
await browser.sleep(1000);
screenLabel = await browser.executeScript<string>(script);
expect(screenLabel).toEqual('custom_fake_screen');
});
});
describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(TestAppConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities)
.setChromeOptions(getBrowserOptionsWithoutDevices())
.usingServer(TestAppConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should camera and microphone buttons be disabled in the prejoin page when permissions are denied', async () => {
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
await utils.waitForElement('#no-video-device-message');
await utils.waitForElement('#no-audio-device-message');
expect(await utils.isPresent('#backgrounds-button')).toBeFalse();
});
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');
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse();
button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse();
});
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();
let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse();
button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse();
});
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');
await browser.sleep(500);
await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue();
await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
await utils.waitForElement('#no-video-device-message');
await utils.clickOn('#audio-opt');
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
await utils.waitForElement('#no-audio-device-message');
});
});

View File

@ -1,380 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('OpenVidu Components ATTRIBUTE toolbar directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the CHAT PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#chatPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if chat button does not exist
expect(await utils.isPresent('chat-panel-btn')).toBeFalse();
});
it('should HIDE the PARTICIPANTS PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#participantsPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if participants button does not exist
expect(await utils.isPresent('participants-panel-btn')).toBeFalse();
});
it('should HIDE the ACTIVITIES PANEL BUTTON', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#activitiesPanelButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Check if participants button does not exist
expect(await utils.isPresent('activities-panel-btn')).toBeFalse();
});
it('should HIDE the DISPLAY LOGO', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#displayLogo-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('branding-logo')).toBeFalse();
});
it('should HIDE the DISPLAY ROOM name', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#displayRoomName-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('session-name')).toBeFalse();
});
it('should HIDE the FULLSCREEN button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#fullscreenButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('#more-options-menu');
// Checking if fullscreen button is not present
expect(await utils.isPresent('#fullscreen-btn')).toBeFalse();
});
it('should HIDE the STREAMING button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#broadcastingButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.clickOn('#more-options-btn');
await browser.sleep(500);
await utils.waitForElement('#more-options-menu');
// Checking if fullscreen button is not present
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
});
it('should HIDE the LEAVE button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#leaveButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('leave-btn')).toBeFalse();
});
it('should HIDE the SCREENSHARE button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#screenshareButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
expect(await utils.isPresent('screenshare-btn')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE stream directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the AUDIO detector', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#displayAudioDetection-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#session-container');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('audio-wave-container')).toBeFalse();
});
it('should HIDE the PARTICIPANT NAME', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#displayParticipantName-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#session-container');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('participant-name-container')).toBeFalse();
});
it('should HIDE the SETTINGS button', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#settingsButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('settings-container')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE participant panels directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the participant MUTE button', async () => {
const fixedSession = `${url}?sessionId=fixedNameTesting`;
await browser.get(`${fixedSession}`);
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#muteButton-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
await utils.clickOn('#participants-panel-btn');
await utils.waitForElement('#participants-container');
// Starting new browser for adding a new participant
const newTabScript = `window.open("${fixedSession}")`;
await browser.executeScript(newTabScript);
// Get tabs opened
const tabs = await browser.getAllWindowHandles();
// Focus on the last tab
browser.switchTo().window(tabs[1]);
await utils.clickOn('#apply-btn');
// Switch to first tab
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('#remote-participant-item');
expect(await utils.isPresent('mute-btn')).toBeFalse();
});
});
describe('OpenVidu Components ATTRIBUTE activity panel directives', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should HIDE the RECORDING activity', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#recordingActivity-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
});
it('should HIDE the STREAMING activity', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#broadcastingActivity-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#activities-panel-btn');
await browser.sleep(500);
await utils.waitForElement('#custom-activities-panel');
await utils.waitForElement('ov-recording-activity');
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
});
});

View File

@ -1,186 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('OpenVidu Components EVENTS', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should receive the onParticipantLeft event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
// Clicking to leave button
await utils.clickOn('#leave-btn');
// Checking if onLeaveButtonClicked has been received
await utils.waitForElement('#onParticipantLeft');
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
});
it('should receive the onVideoEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#camera-btn');
await utils.waitForElement('#onVideoEnabledChanged');
expect(await utils.isPresent('#onVideoEnabledChanged')).toBeTrue();
});
it('should receive the onAudioEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#mic-btn');
await utils.waitForElement('#onAudioEnabledChanged');
expect(await utils.isPresent('#onAudioEnabledChanged')).toBeTrue();
});
it('should receive the onScreenShareEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.clickOn('#screenshare-btn');
await utils.waitForElement('#onScreenShareEnabledChanged');
expect(await utils.isPresent('#onScreenShareEnabledChanged')).toBeTrue();
});
it('should receive the onFullscreenEnabledChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.toggleFullscreenFromToolbar();
await browser.sleep(1000);
await utils.waitForElement('#onFullscreenEnabledChanged');
expect(await utils.isPresent('#onFullscreenEnabledChanged')).toBeTrue();
});
it('should receive the onRecordingStartRequested event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.toggleRecordingFromToolbar();
await utils.waitForElement('#onRecordingStartRequested');
expect(await utils.isPresent('#onRecordingStartRequested')).toBeTrue();
});
it('should receive the onParticipantsPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('participants');
await utils.waitForElement('#onParticipantsPanelStatusChanged');
expect(await utils.isPresent('#onParticipantsPanelStatusChanged')).toBeTrue();
});
it('should receive the onChatPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('chat');
await utils.waitForElement('#onChatPanelStatusChanged');
expect(await utils.isPresent('#onChatPanelStatusChanged')).toBeTrue();
});
it('should receive the onActivitiesPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('activities');
await utils.waitForElement('#onActivitiesPanelStatusChanged');
expect(await utils.isPresent('#onActivitiesPanelStatusChanged')).toBeTrue();
});
it('should receive the onSettingsPanelStatusChanged event', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('#onSettingsPanelStatusChanged');
expect(await utils.isPresent('#onSettingsPanelStatusChanged')).toBeTrue();
});
});

View File

@ -1,806 +0,0 @@
import { Builder, By, WebDriver } from 'selenium-webdriver';
import { NestedConfig } from '../selenium.conf';
import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl;
describe('E2E: Toolbar structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render only the custom toolbar (no additional buttons, no default toolbar)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has not been rendered
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeFalse();
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render the custom toolbar with additional custom buttons and hide the default toolbar', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#ovToolbarAdditionalButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has been rendered;
await utils.waitForElement('#custom-toolbar-additional-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeTrue();
const element = await browser.findElements(By.id('toolbar-additional-btn'));
expect(element.length).toEqual(2);
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render the custom toolbar with additional custom panel buttons and hide the default toolbar', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox');
await utils.clickOn('#ovToolbarAdditionalPanelButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom toolbar is present in DOM
await utils.waitForElement('#custom-toolbar');
expect(await utils.isPresent('#custom-toolbar')).toBeTrue();
// Check if additional buttons element has been rendered;
await utils.waitForElement('#custom-toolbar-additional-panel-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
const element = await browser.findElements(By.id('toolbar-additional-panel-btn'));
expect(element.length).toEqual(1);
// Check if default toolbar is not present
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
});
it('should render only additional toolbar buttons (default toolbar visible, no custom toolbar)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovToolbarAdditionalButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if default toolbar is present
await utils.waitForElement('#default-toolbar');
expect(await utils.isPresent('#default-toolbar')).toBeTrue();
// Check if additional buttons are present
await utils.waitForElement('#custom-toolbar-additional-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-buttons')).toBeTrue();
element = await browser.findElements(By.id('toolbar-additional-btn'));
expect(element.length).toEqual(3);
// Check if custom toolbar not is present
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
});
it('should render only additional toolbar panel buttons (default toolbar visible, no custom toolbar)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovToolbarAdditionalPanelButtons-checkbox');
await utils.clickOn('#apply-btn');
// Check if default toolbar is present
await utils.waitForElement('#default-toolbar');
expect(await utils.isPresent('#default-toolbar')).toBeTrue();
// Check if additional buttons are present
await utils.waitForElement('#custom-toolbar-additional-panel-buttons');
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
element = await browser.findElements(By.id('toolbar-additional-panel-btn'));
expect(element.length).toEqual(1);
// Check if custom toolbar not is present
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
});
});
describe('E2E: Panel structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render an additional custom panel with default panels', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Open additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
await browser.sleep(500);
// Check if custom panel is present
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
element = await utils.waitForElement('#additional-panel-title');
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
// Open the participants panel
await utils.clickOn('#participants-panel-btn');
await browser.sleep(500);
// Check if default panel is present
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Open additional panel again
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
// Close the additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render only the custom panel container (no children, no default panels)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with an additional panel only', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
// Check if custom panel is present
element = await browser.findElements(By.id('custom-additional-panel'));
expect(element.length).toEqual(1);
element = await utils.waitForElement('#additional-panel-title');
expect(await utils.isPresent('#additional-panel-title')).toBeTrue();
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render the custom panel container with a custom chat panel only', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
it('should render the custom panel container with a custom activities panel only', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening chat panel
await utils.clickOn('#activities-panel-btn');
// Check if default activities panel is not present
expect(await utils.isPresent('#default-activities-panel')).toBeFalse();
// Check if custom chat panel is not present
element = await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('#custom-activities-panel')).toBeTrue();
element = await utils.waitForElement('#activities-panel-title');
expect(await element.getAttribute('innerText')).toEqual('CUSTOM ACTIVITIES PANEL');
});
it('should render the custom panel container with a custom participants panel only (no children)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with a custom participants panel and a custom participant item', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Check if custom participant panel item is present
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Check if default participant panel item is not present
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
});
it('should render the custom panel container with a custom participants panel, custom participant item, and custom item element', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Check if custom participant panel item is present
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Check if custom participant panel item element is present
await utils.waitForElement('#custom-participants-panel-item-element');
expect(await utils.isPresent('#custom-participants-panel-item-element')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if default participant panel item is not present
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
});
it('should render only a custom activities panel (no default panel)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#activities-panel-btn');
// Check if default panel is not present
expect(await utils.isPresent('#default-activities-panel')).toBeFalse();
// Check if custom panel is present
await utils.waitForElement('#custom-activities-panel');
expect(await utils.isPresent('#custom-activities-panel')).toBeTrue();
// Check if activities panel is has content
await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).toBeTrue();
});
it('should render only a custom additional panel (no default panel)', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-additional-panel');
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
element = await utils.waitForElement('#additional-panel-title');
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render only a custom chat panel (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
it('should render only a custom participants panel (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render only a custom participant panel item (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItem-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
expect(await utils.isPresent('#custom-panels')).toBeFalse();
// Check if default panel is not present
await utils.waitForElement('#default-panel');
expect(await utils.isPresent('#default-panel')).toBeTrue();
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
await utils.waitForElement('#custom-participants-panel-item');
expect(await utils.isPresent('#custom-participants-panel-item')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render only a custom participant panel item element (no custom panel container)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if default participant panel is not present
await utils.waitForElement('#default-participants-panel');
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Check if custom participant panel is not present
expect(await utils.isPresent('#custom-participants-panel')).toBeFalse();
expect(await utils.isPresent('#custom-participants-panel-item')).toBeFalse();
expect(await utils.isPresent('#custom-participants-panel-item')).toBeFalse();
await utils.waitForElement('#custom-participants-panel-item-element');
expect(await utils.isPresent('#custom-participants-panel-item-element')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is present
await utils.waitForElement('#default-chat-panel');
expect(await utils.isPresent('#default-chat-panel')).toBeTrue();
// Check if custom chat panel is not present;
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
});
it('should render the custom panel container with both custom chat and participants panels', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox');
await utils.clickOn('#ovChatPanel-checkbox');
await utils.clickOn('#ovParticipantsPanel-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Click on button for opening participants panel
await utils.clickOn('#participants-panel-btn');
// Check if custom panel is present
await utils.waitForElement('#custom-panels');
expect(await utils.isPresent('#custom-panels')).toBeTrue();
// Check if default panel is not present
expect(await utils.isPresent('#default-panel')).toBeFalse();
// Check if default participant panel is not present
expect(await utils.isPresent('#default-participant-panel')).toBeFalse();
// Check if custom participant panel is present
await utils.waitForElement('#custom-participants-panel');
expect(await utils.isPresent('#custom-participants-panel')).toBeTrue();
// Click on button for opening chat panel
await utils.clickOn('#chat-panel-btn');
// Check if default chat panel is not present
expect(await utils.isPresent('#default-chat-panel')).toBeFalse();
// Check if custom chat panel is present
await utils.waitForElement('#custom-chat-panel');
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
});
});
describe('E2E: Layout and stream structural directive scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder()
.forBrowser(NestedConfig.browserName)
.withCapabilities(NestedConfig.browserCapabilities)
.setChromeOptions(NestedConfig.browserOptions)
.usingServer(NestedConfig.seleniumAddress)
.build();
}
beforeEach(async () => {
browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser);
});
afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should render only the custom layout (no stream, no default layout)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom layout is present
await utils.waitForElement('#custom-layout');
expect(await utils.isPresent('#custom-layout')).toBeTrue();
// Check if default layout is not present
expect(await utils.isPresent('#default-layout')).toBeFalse();
// Check if custom stream is not present
expect(await utils.isPresent('#custom-stream')).toBeFalse();
// Check if video is not present
expect(await utils.isPresent('video')).toBeFalse();
});
it('should render the custom layout with a custom stream (no default layout/stream)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox');
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#apply-btn');
// Check if custom layout is present
await utils.waitForElement('#custom-layout');
expect(await utils.isPresent('#custom-layout')).toBeTrue();
// Check if default layout is not present
expect(await utils.isPresent('default-layout')).toBeFalse();
// Check if custom stream is present
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('#custom-stream')).toBeTrue();
// Check if default stream is not present
expect(await utils.isPresent('default-stream')).toBeFalse();
// Check if video is present
await utils.waitForElement('video');
expect(await utils.isPresent('video')).toBeTrue();
});
it('should render only a custom stream (no custom layout, no default stream)', async () => {
await browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox');
await utils.clickOn('#apply-btn');
// Check if default layout is not present
await utils.waitForElement('#default-layout');
expect(await utils.isPresent('#default-layout')).toBeTrue();
// Check if custom stream is present
await utils.waitForElement('#custom-stream');
expect(await utils.isPresent('#custom-stream')).toBeTrue();
// Check if custom layout is not present
expect(await utils.isPresent('#custom-layout')).toBeFalse();
// Check if default stream is not present
expect(await utils.isPresent('default-stream')).toBeFalse();
// Check if video is present
await utils.waitForElement('video');
expect(await utils.isPresent('video')).toBeTrue();
});
});

View File

@ -1,132 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Panels: UI Navigation and Section Switching', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should open and close the CHAT panel and verify its content', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
await utils.waitForElement('.messages-container');
expect(await utils.isPresent('.messages-container')).toBeTrue();
await chatButton.click();
expect(await utils.isPresent('.input-container')).toBeFalse();
expect(await utils.isPresent('.messages-container')).toBeFalse();
});
it('should open and close the PARTICIPANTS panel and verify its content', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.local-participant-container');
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
await utils.waitForElement('ov-participant-panel-item');
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
await participantBtn.click();
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
});
it('should open and close the ACTIVITIES panel and verify its content', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const activitiesBtn = await utils.waitForElement('#activities-panel-btn');
await activitiesBtn.click();
await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).toBeTrue();
await utils.waitForElement('#recording-activity');
expect(await utils.isPresent('#recording-activity')).toBeTrue();
await activitiesBtn.click();
expect(await utils.isPresent('#activities-container')).toBeFalse();
expect(await utils.isPresent('#recording-activity')).toBeFalse();
});
it('should open the SETTINGS panel and verify its content', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
element = await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('#default-settings-panel')).toBeTrue();
});
it('should switch between PARTICIPANTS and CHAT panels and verify correct content is shown', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent();
const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue();
const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
await chatButton.click();
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue();
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
await chatButton.click();
expect(await utils.getNumberOfElements('.input-container')).toEqual(0);
expect(await utils.isPresent('messages-container')).toBeFalse();
});
it('should switch between sections in the SETTINGS panel and verify correct content is shown', async () => {
let element;
await browser.get(`${url}&prejoin=false`);
await utils.checkToolbarIsPresent();
await utils.togglePanel('settings');
await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
await browser.sleep(500);
element = await utils.waitForElement('#general-opt');
await element.click();
expect(await utils.isPresent('ov-participant-name-input')).toBeTrue();
element = await utils.waitForElement('#video-opt');
await element.click();
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
element = await utils.waitForElement('#audio-opt');
await element.click();
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
});
});

View File

@ -1,316 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('E2E: Screensharing features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should toggle screensharing on and off twice, updating video count', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Enable screensharing
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable screensharing
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
// Enable again
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable again
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
});
it('should show screenshare and muted camera (camera off, screenshare on)', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Mute camera
await utils.waitForElement('#camera-btn');
await utils.clickOn('#camera-btn');
// Enable screensharing
const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue();
await screenshareButton.click();
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable screensharing
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1);
});
it('should display screensharing with a single pinned video', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Enable screensharing
const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue();
await screenshareButton.click();
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
});
it('should replace pinned video when a second participant starts screensharing', async () => {
const roomName = 'screensharingE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
// First participant screenshares
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Second participant joins and screenshares
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Switch back to first tab and check
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
});
it('should unpin screensharing and restore previous pinned video when disabled', async () => {
const roomName = 'screensharingtwoE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
// First participant screenshares
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Second participant joins and screenshares
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Disable screensharing for second participant
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Switch back to first tab and check
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(3);
await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
});
it('should correctly share screen with microphone muted and maintain proper track state', async () => {
// Helper for inspecting stream tracks
const getMediaTracks = (className: string) => {
return `
const tracks = document.getElementsByClassName('${className}')[0].srcObject.getTracks();
return tracks.map(track => ({
kind: track.kind,
enabled: track.enabled,
id: track.id,
label: track.label
}));`;
};
// Setup: Navigate to room and skip prejoin
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Step 1: First mute the microphone
const micButton = await utils.waitForElement('#mic-btn');
await micButton.click();
// Step 2: Start screen sharing
await utils.clickOn('#screenshare-btn');
// Step 3: Verify both streams are present
await utils.waitForElement('.screen-type');
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Step 4: Verify screen share track properties
const screenTracks: any[] = await browser.executeScript(getMediaTracks('screen-type'));
expect(screenTracks.length).toEqual(1);
expect(screenTracks[0].kind).toEqual('video');
expect(screenTracks[0].enabled).toBeTrue();
// Step 5: Verify microphone status indicators for both streams
// await utils.waitForElement('#status-mic');
// const micStatusCount = await utils.getNumberOfElements('#status-mic');
// expect(micStatusCount).toEqual(2);
// Step 6: Stop screen sharing and verify stream count
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.clickOn('#disable-screen-button');
await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(1);
});
// it('should show and hide CAMERA stream when muting video with screensharing', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkLayoutPresent();
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// expect(await screenshareButton.isDisplayed()).toBeTrue();
// await screenshareButton.click();
// await utils.waitForElement('.OV_big');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// });
// it('should screenshare has audio active when camera is muted', async () => {
// let isAudioEnabled;
// const audioEnableScript = 'return document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0].enabled;';
// await browser.get(`${url}&prejoin=false`);
// await utils.checkLayoutPresent();
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
// await screenshareButton.click();
// await utils.waitForElement('.OV_big');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// // Muting camera video
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// await browser.sleep(500);
// expect(await utils.isPresent('#status-mic')).toBeFalse();
// // Checking if audio is muted after join the room
// isAudioEnabled = await browser.executeScript(audioEnableScript);
// expect(isAudioEnabled).toBeTrue();
// // Unmuting camera
// await muteVideoButton.click();
// await browser.sleep(1000);
// await utils.waitForElement('.camera-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// });
// it('should camera come back with audio muted when screensharing', async () => {
// let element, isAudioEnabled;
// const getAudioScript = (className: string) => {
// return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
// };
// await browser.get(`${url}&prejoin=false`);
// await utils.checkLayoutPresent();
// // Clicking to screensharing button
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
// await screenshareButton.click();
// await utils.waitForElement('.screen-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// // Mute camera
// const muteVideoButton = await utils.waitForElement('#camera-btn');
// await muteVideoButton.click();
// expect(await utils.getNumberOfElements('video')).toEqual(1);
// expect(await utils.isPresent('#status-mic')).toBeFalse();
// // Checking if audio is muted after join the room
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
// expect(isAudioEnabled).toBeTrue();
// // Mute audio
// const muteAudioButton = await utils.waitForElement('#mic-btn');
// await muteAudioButton.click();
// await utils.waitForElement('#status-mic');
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(1);
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
// expect(isAudioEnabled).toBeFalse();
// // Unmute camera
// await muteVideoButton.click();
// await utils.waitForElement('.camera-type');
// expect(await utils.getNumberOfElements('video')).toEqual(2);
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(2);
// isAudioEnabled = await browser.executeScript(getAudioScript('camera-type'));
// expect(isAudioEnabled).toBeFalse();
// });
});

View File

@ -1,76 +0,0 @@
import { Capabilities } from 'selenium-webdriver';
import * as chrome from 'selenium-webdriver/chrome';
import { LAUNCH_MODE } from './config';
interface BrowserConfig {
appUrl: string;
seleniumAddress: string;
browserCapabilities: Capabilities;
browserOptions: chrome.Options;
browserName: string;
}
const audioPath = LAUNCH_MODE === 'CI' ? `e2e-assets/audio_test.wav` : 'e2e/assets/audio_test.wav';
const chromeArguments = [
'--window-size=1300,1000',
// '--headless',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
`--use-file-for-fake-audio-capture=${audioPath}`
];
const chromeArgumentsCI = [
'--window-size=1300,1000',
'--headless',
'--no-sandbox',
'--disable-gpu',
'--disable-popup-blocking',
'--no-first-run',
'--no-default-browser-check',
'--disable-dev-shm-usage',
'--disable-background-networking',
'--disable-default-apps',
'--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream',
`--use-file-for-fake-audio-capture=${audioPath}`,
'--autoplay-policy=no-user-gesture-required',
'--allow-file-access-from-files'
];
const chromeArgumentsWithoutMediaDevices = ['--headless', '--window-size=1300,900', '--deny-permission-prompts'];
const chromeArgumentsWithoutMediaDevicesCI = [
'--window-size=1300,900',
'--headless',
'--no-sandbox',
'--disable-gpu',
'--disable-popup-blocking',
'--no-first-run',
'--no-default-browser-check',
'--disable-dev-shm-usage',
'--disable-background-networking',
'--disable-default-apps',
'--deny-permission-prompts'
];
export const TestAppConfig: BrowserConfig = {
appUrl: 'http://localhost:4200/#/call?staticVideos=false',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
browserName: 'chrome',
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),
browserOptions: new chrome.Options().addArguments(...(LAUNCH_MODE === 'CI' ? chromeArgumentsCI : chromeArguments))
};
export const NestedConfig: BrowserConfig = {
appUrl: 'http://localhost:4200/#/testing',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
browserName: 'Chrome',
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),
browserOptions: new chrome.Options().addArguments(...(LAUNCH_MODE === 'CI' ? chromeArgumentsCI : chromeArguments))
};
export function getBrowserOptionsWithoutDevices() {
if (LAUNCH_MODE === 'CI') {
return new chrome.Options().addArguments(...chromeArgumentsWithoutMediaDevicesCI);
} else {
return new chrome.Options().addArguments(...chromeArgumentsWithoutMediaDevices);
}
}

View File

@ -1,773 +0,0 @@
import { Builder, ILocation, IRectangle, ISize, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Stream rendering and media toggling scenarios', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should not render any video element when joining with video disabled', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
});
it('should render a video element but no audio when joining with audio muted', async () => {
await browser.get(`${url}&prejoin=true&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should render both video and audio elements when joining with both enabled', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
});
it('should add a screen share video/audio when sharing screen with both camera and mic muted', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=false`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(1); //screen sharse video
expect(await utils.getNumberOfElements('audio')).toEqual(1); //screen share audio
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should add a screen share video/audio when sharing screen with both camera and mic enabled', async () => {
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); //screen share audio and local audio
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
});
/* ------------ Checking video/audio elements with two participants ------------ */
it('should not render any video/audio elements when two participants join with both video and audio muted', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
await utils.waitForElement('.OV_stream.remote');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should render two video elements and no audio when two participants join with audio muted', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote');
await browser.sleep(2000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should not render any video elements but should render two audio elements when two participants join with video disabled', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(1);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(1);
const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote');
await browser.sleep(2000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
await browser.switchTo().window(tabs[1]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
});
it('should add a screen share video/audio when a participant with both video and audio muted shares their screen (two participants)', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
await browser.switchTo().window(tabs[1]);
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(0);
expect(await utils.getNumberOfElements('audio')).toEqual(0);
});
it('should add a screen share video/audio when a remote participant with both video and audio enabled shares their screen', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
await utils.clickOn('#screenshare-btn');
await browser.sleep(1000);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(3);
expect(await utils.getNumberOfElements('audio')).toEqual(3); // screen share audios and local audio and remote audio
await browser.switchTo().window(tabs[0]);
await browser.sleep(1000);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(3);
expect(await utils.getNumberOfElements('audio')).toEqual(3); // screen share audios and local audio and remote audio
await browser.switchTo().window(tabs[1]);
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2);
});
it('should add a screen share video/audio for both participants when both share their screen with video/audio muted', async () => {
const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]);
await utils.waitForElement('.OV_stream.local');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
await utils.clickOn('#screenshare-btn');
await browser.sleep(500);
await utils.waitForElement('#local-element-screen_share');
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(4);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); // screen share audios
await browser.switchTo().window(tabs[0]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(4);
expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('audio')).toEqual(2); // screen share audios
await utils.disableScreenShare();
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
await browser.switchTo().window(tabs[1]);
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream')).toEqual(3);
expect(await utils.getNumberOfElements('video')).toEqual(1);
expect(await utils.getNumberOfElements('audio')).toEqual(1); // screen share audios
});
});
describe('Stream UI controls and interaction features', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should show the PIN button over the LOCAL video', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#pin-btn');
expect(await utils.isPresent('#pin-btn')).toBeTrue();
});
it('should show the PIN button over the REMOTE video', async () => {
const roomName = 'pinE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.waitForElement('.OV_stream.remote');
await utils.hoverOn('.OV_stream.remote');
await utils.waitForElement('#pin-btn');
expect(await utils.isPresent('#pin-btn')).toBeTrue();
});
it('should show the SILENCE button ONLY over the REMOTE video', async () => {
const roomName = 'silenceE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).toEqual(0);
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.remote');
await utils.hoverOn('.OV_stream.remote');
await utils.waitForElement('.OV_stream.remote #silence-btn');
expect(await utils.isPresent('.OV_stream.remote #silence-btn')).toBeTrue();
expect(await utils.getNumberOfElements('.OV_stream.remote #silence-btn')).toEqual(1);
await utils.hoverOn('.OV_stream.local');
await browser.sleep(500);
expect(await utils.getNumberOfElements('.OV_stream.local #silence-btn')).toEqual(0);
});
it('should show the MINIMIZE button ONLY over the LOCAL video', async () => {
const roomName = 'minimizeE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
expect(await utils.isPresent('#minimize-btn')).toBeTrue();
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.remote');
expect(await utils.getNumberOfElements('#minimize-btn')).toEqual(0);
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
expect(await utils.isPresent('#minimize-btn')).toBeTrue();
});
it('should minimize the LOCAL video', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const stream = await utils.waitForElement('.OV_stream.local');
const streamProps: IRectangle = await stream.getRect();
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(900);
const minimizeStream = await utils.waitForElement('.OV_stream.local');
const minimizeStreamProps: IRectangle = await minimizeStream.getRect();
expect(streamProps.height).not.toEqual(minimizeStreamProps.height);
expect(streamProps.width).not.toEqual(minimizeStreamProps.width);
expect(minimizeStreamProps.x).toBeLessThan(100);
expect(minimizeStreamProps.y).toBeLessThan(100);
});
it('should MAXIMIZE the local video', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
const marginX = 5;
await utils.checkLayoutPresent();
await utils.subscribeToDropEvent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(500);
await utils.dragToRight(300, 300);
await browser.sleep(500);
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(300 + marginX);
expect(streamProps.y).toEqual(300);
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(1500);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(300 + marginX);
expect(streamProps.y).toEqual(1); //.OV_publisher element has 1px of padding
});
it('should be able to dragg the minimized LOCAL video', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
const marginX = 5;
await utils.checkLayoutPresent();
await utils.subscribeToDropEvent();
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(500);
await utils.dragToRight(300, 300);
await browser.sleep(500);
const stream = await utils.waitForElement('.OV_stream.local');
const streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(300 + marginX);
expect(streamProps.y).toEqual(300);
});
it('should be the MINIMIZED video ALWAYS VISIBLE when toggling panels', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
const marginX = 5;
await utils.checkLayoutPresent();
await utils.subscribeToDropEvent();
// Minimize stream element
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(500);
await utils.dragToRight(900, 0);
await browser.sleep(500);
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(900 + marginX);
expect(streamProps.y).toEqual(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeLessThan(900 + marginX);
expect(streamProps.y).toEqual(0);
// Close chat panel
await utils.clickOn('#chat-panel-btn');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeGreaterThanOrEqual(lastX + marginX);
expect(streamProps.y).toEqual(0);
});
it('should be the MINIMIZED video go to the right when panel closes', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
const waitTimeout = 1000;
const marginX = 5;
const newX = 641 - marginX;
await utils.checkLayoutPresent();
await utils.subscribeToDropEvent();
// Open chat panel
await utils.waitForElement('.OV_stream.local');
await utils.waitForElement('#chat-panel-btn');
await utils.clickOn('#chat-panel-btn');
// Minimize stream element
await browser.sleep(waitTimeout);
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(waitTimeout);
await utils.dragToRight(newX, 0);
await browser.sleep(waitTimeout);
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(newX + marginX);
expect(streamProps.y).toEqual(0);
// Close chat panel
// There is a unstable behaviour simulating the drag and drop with selenium (the stream is not moved the first time)
// So we are going to open and close the chat panel two times to ensure the stream is moved
await utils.clickOn('#chat-panel-btn');
await browser.sleep(waitTimeout);
await utils.clickOn('#chat-panel-btn');
await browser.sleep(waitTimeout);
await utils.clickOn('#chat-panel-btn');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeGreaterThanOrEqual(newX + marginX);
expect(streamProps.y).toEqual(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
await browser.sleep(waitTimeout);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(lastX + marginX);
expect(streamProps.y).toEqual(0);
});
it('should be the MINIMIZED video ALWAYS VISIBLE when toggling from small to bigger panel', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
const marginX = 5;
await utils.checkLayoutPresent();
await utils.subscribeToDropEvent();
// Minimize stream element
await utils.waitForElement('.OV_stream.local');
await utils.hoverOn('.OV_stream.local');
await utils.waitForElement('#minimize-btn');
await utils.clickOn('#minimize-btn');
await browser.sleep(500);
await utils.dragToRight(900, 0);
await browser.sleep(500);
let stream = await utils.waitForElement('.OV_stream.local');
let streamProps: IRectangle = await stream.getRect();
expect(streamProps.x).toEqual(900 + marginX);
expect(streamProps.y).toEqual(0);
// Open chat panel
await utils.clickOn('#chat-panel-btn');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
let lastX = streamProps.x;
expect(streamProps.x).toBeLessThan(900 + marginX);
expect(streamProps.y).toEqual(0);
// Open settings panel
await utils.togglePanel('settings');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeLessThan(lastX + marginX);
expect(streamProps.y).toEqual(0);
lastX = streamProps.x;
// Open chat panel
await utils.clickOn('#chat-panel-btn');
await browser.sleep(1000);
stream = await utils.waitForElement('.OV_stream.local');
streamProps = await stream.getRect();
expect(streamProps.x).toBeGreaterThanOrEqual(lastX + marginX);
expect(streamProps.y).toEqual(0);
});
it('should show the audio detection elements when participant is speaking', async () => {
const roomName = 'speakingE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(`${fixedUrl}&audioEnabled=false`);
await utils.checkLayoutPresent();
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
await browser.sleep(1000);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote.speaking');
expect(await utils.getNumberOfElements('.OV_stream.remote.speaking')).toEqual(1);
// Check only one element is marked as speaker due to the local participant is muted
await utils.waitForElement('.OV_stream.speaking');
expect(await utils.getNumberOfElements('.OV_stream.speaking')).toEqual(1);
});
});
describe('Video playback reliability with different media states', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should play the participant video with only audio', async () => {
const roomName = 'audioOnlyE2E';
const fixedUrl = `${url}&roomName=${roomName}`;
await browser.get(fixedUrl);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#camera-button');
await utils.clickOn('#join-button');
// Go to first tab
await browser.switchTo().window(tabs[0]);
// Wait until NO_STREAM_PLAYING_EVENT exception timeout is reached
await browser.sleep(6000);
const exceptionQuantity = await utils.getNumberOfElements('#NO_STREAM_PLAYING_EVENT');
expect(exceptionQuantity).toEqual(0);
});
it('should play the participant video with only video', async () => {
const roomName = 'videoOnlyE2E';
const fixedUrl = `${url}&roomName=${roomName}`;
await browser.get(fixedUrl);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button');
// Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]);
await utils.checkPrejoinIsPresent();
await utils.clickOn('#microphone-button');
await utils.clickOn('#join-button');
// Go to first tab
await browser.switchTo().window(tabs[0]);
// Wait until NO_STREAM_PLAYING_EVENT exception timeout is reached
await browser.sleep(6000);
const exceptionQuantity = await utils.getNumberOfElements('#NO_STREAM_PLAYING_EVENT');
expect(exceptionQuantity).toEqual(0);
});
});

View File

@ -1,64 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Toolbar button functionality for local media control', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should toggle mute/unmute on the local microphone and update the icon accordingly', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const micButton = await utils.waitForElement('#mic-btn');
await micButton.click();
await utils.waitForElement('#mic-btn #mic_off');
expect(await utils.isPresent('#mic-btn #mic_off')).toBeTrue();
await micButton.click();
await utils.waitForElement('#mic-btn #mic');
expect(await utils.isPresent('#mic-btn #mic')).toBeTrue();
});
it('should toggle mute/unmute on the local camera and update the icon accordingly', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
const cameraButton = await utils.waitForElement('#camera-btn');
await cameraButton.click();
await utils.waitForElement('#camera-btn #videocam_off');
expect(await utils.isPresent('#camera-btn #videocam_off')).toBeTrue();
await cameraButton.click();
await utils.waitForElement('#camera-btn #videocam');
expect(await utils.isPresent('#camera-btn #videocam')).toBeTrue();
});
});

View File

@ -1,13 +0,0 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"strict": true,
"outDir": "./dist",
"lib": ["es6"],
"types": [ "jasmine", "node" ],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
}

View File

@ -1,282 +0,0 @@
import { By, until, WebDriver, WebElement } from 'selenium-webdriver';
import * as fs from 'fs';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
type PNGWithMetadata = PNG & { data: Buffer };
export class OpenViduComponentsPO {
private TIMEOUT = 10 * 1000;
private POLL_TIMEOUT = 1 * 1000;
constructor(private browser: WebDriver) {}
async waitForElement(selector: string): Promise<WebElement> {
return await this.browser.wait(
until.elementLocated(By.css(selector)),
this.TIMEOUT,
`Time out waiting for ${selector}`,
this.POLL_TIMEOUT
);
}
async getNumberOfElements(selector: string): Promise<number> {
return (await this.browser.findElements(By.css(selector))).length;
}
async isPresent(selector: string): Promise<boolean> {
const elements = await this.browser.findElements(By.css(selector));
return elements.length > 0;
}
async sendKeys(selector: string, keys: string): Promise<void> {
const element = await this.waitForElement(selector);
await element.sendKeys(keys);
}
async checkPrejoinIsPresent(): Promise<void> {
await this.waitForElement('#prejoin-container');
expect(await this.isPresent('#prejoin-container')).toBe(true);
}
async checkSessionIsPresent() {
await this.waitForElement('#call-container');
expect(await this.isPresent('#call-container')).toBe(true);
await this.waitForElement('#session-container');
expect(await this.isPresent('#session-container')).toBe(true);
}
async checkLayoutPresent(): Promise<void> {
await this.waitForElement('#layout-container');
expect(await this.isPresent('#layout-container')).toBe(true);
await this.waitForElement('#layout');
expect(await this.isPresent('#layout')).toBe(true);
}
async checkStreamIsPresent(): Promise<void> {
await this.waitForElement('.OV_stream');
expect(await this.isPresent('.OV_stream')).toBe(true);
}
async checkVideoElementIsPresent(): Promise<void> {
await this.waitForElement('video');
expect(await this.isPresent('video')).toBe(true);
}
async checkToolbarIsPresent(): Promise<void> {
await this.waitForElement('#toolbar');
await this.waitForElement('#media-buttons-container');
expect(await this.isPresent('#media-buttons-container')).toBe(true);
}
async chceckProFeatureAlertIsPresent(): Promise<void> {
await this.waitForElement('ov-pro-feature-template');
expect(await this.isPresent('ov-pro-feature-template')).toBe(true);
}
async clickOn(selector: string): Promise<void> {
const element = await this.waitForElement(selector);
await element.click();
}
async hoverOn(selector: string): Promise<void> {
const element = await this.waitForElement(selector);
const action = this.browser.actions().move({ origin: element, duration: 1000 });
return action.perform();
}
async openTab(url: string): Promise<string[]> {
const newTabScript = `window.open("${url}")`;
await this.browser.executeScript(newTabScript);
return this.browser.getAllWindowHandles();
}
async subscribeToDropEvent(): Promise<void> {
const script = `
document.dispatchEvent(new Event("webcomponentTestingEndedDragAndDropEvent"));
`;
await this.browser.executeScript(script);
}
async dragToRight(x: number, y: number): Promise<void> {
const script = `
document.dispatchEvent(new CustomEvent("webcomponentTestingEndedDragAndDropRightEvent", {detail: {x: arguments[0], y: arguments[1]}}));
`;
await this.browser.executeScript(script, x, y);
}
async toggleToolbarMoreOptions(): Promise<void> {
await this.waitForElement('#more-options-btn');
expect(await this.isPresent('#more-options-btn')).toBe(true);
await this.clickOn('#more-options-btn');
await this.browser.sleep(500);
await this.waitForElement('#more-options-menu');
}
async disableScreenShare(): Promise<void> {
await this.waitForElement('#screenshare-btn');
await this.clickOn('#screenshare-btn');
await this.browser.sleep(500);
await this.waitForElement('#screenshare-menu');
await this.clickOn('#disable-screen-button');
await this.browser.sleep(1000);
}
async toggleRecordingFromToolbar() {
// Open more options menu
await this.toggleToolbarMoreOptions();
await this.waitForElement('#recording-btn');
expect(await this.isPresent('#recording-btn')).toBe(true);
await this.clickOn('#recording-btn');
}
async toggleFullscreenFromToolbar() {
// Open more options menu
await this.toggleToolbarMoreOptions();
await this.waitForElement('#fullscreen-btn');
expect(await this.isPresent('#fullscreen-btn')).toBe(true);
await this.clickOn('#fullscreen-btn');
}
async leaveRoom() {
try {
// Close any open panels or menus clicking on the body
await this.clickOn('body');
await this.browser.sleep(300);
// Verify that the leave button is present
await this.waitForElement('#leave-btn');
// Click on the leave button
await this.clickOn('#leave-btn');
// Verify that the session container is no longer present
await this.browser.wait(
async () => {
return !(await this.isPresent('#session-container'));
},
this.TIMEOUT,
'Session container should disappear after leaving room'
);
// Wait for the prejoin container to be present again
await this.browser.sleep(500);
// Verify that there are no video elements left in the DOM
const videoCount = await this.getNumberOfElements('video');
if (videoCount > 0) {
console.warn(`Warning: ${videoCount} video elements still present after leaving room`);
}
} catch (error) {
console.error('Error during leaveRoom:', error);
throw error;
}
}
async togglePanel(panelName: string) {
switch (panelName) {
case 'activities':
await this.waitForElement('#activities-panel-btn');
expect(await this.isPresent('#activities-panel-btn')).toBe(true);
await this.clickOn('#activities-panel-btn');
break;
case 'chat':
await this.waitForElement('#chat-panel-btn');
await this.clickOn('#chat-panel-btn');
break;
case 'participants':
await this.waitForElement('#participants-panel-btn');
await this.clickOn('#participants-panel-btn');
break;
case 'backgrounds':
await this.waitForElement('#more-options-btn');
await this.clickOn('#more-options-btn');
await this.browser.sleep(500);
await this.waitForElement('#virtual-bg-btn');
await this.clickOn('#virtual-bg-btn');
await this.browser.sleep(1000);
break;
case 'settings':
await this.toggleToolbarMoreOptions();
await this.waitForElement('#toolbar-settings-btn');
await this.clickOn('#toolbar-settings-btn');
break;
}
await this.browser.sleep(500);
}
async applyBackground(bgId: string) {
await this.waitForElement('ov-background-effects-panel');
await this.browser.sleep(1000);
await this.waitForElement(`#effect-${bgId}`);
await this.clickOn(`#effect-${bgId}`);
await this.browser.sleep(2000);
}
async applyVirtualBackgroundFromPrejoin(bgId: string): Promise<void> {
await this.waitForElement('#backgrounds-button');
await this.clickOn('#backgrounds-button');
await this.applyBackground(bgId);
await this.clickOn('#backgrounds-button');
}
async saveScreenshot(filename: string, element: WebElement) {
const image = await element.takeScreenshot();
fs.writeFileSync(filename, image, 'base64');
}
async expectVirtualBackgroundApplied(
img1Name: string,
img2Name: string,
{
threshold = 0.4,
minDiffPixels = 500,
debug = false
}: {
threshold?: number;
minDiffPixels?: number;
debug?: boolean;
} = {}
): Promise<void> {
const beforeImg = PNG.sync.read(fs.readFileSync(img1Name));
const afterImg = PNG.sync.read(fs.readFileSync(img2Name));
const { width, height } = beforeImg;
const diff = new PNG({ width, height });
// const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, width, height, {
// threshold: 0.4
// // alpha: 0.5,
// // includeAA: false,
// // diffColor: [255, 0, 0]
// });
const numDiffPixels = pixelmatch(beforeImg.data, afterImg.data, diff.data, width, height, {
threshold
// includeAA: true
});
if (numDiffPixels <= minDiffPixels) {
// Sólo guardar los archivos de debug si falla la prueba
if (debug) {
fs.writeFileSync('before.png', PNG.sync.write(beforeImg));
fs.writeFileSync('after.png', PNG.sync.write(afterImg));
fs.writeFileSync('diff.png', PNG.sync.write(diff));
}
}
expect(numDiffPixels).toBeGreaterThan(minDiffPixels, 'The virtual background was not applied correctly');
// fs.writeFileSync('diff.png', PNG.sync.write(diff));
// expect(numDiffPixels).to.be.greaterThan(500, 'The virtual background was not applied correctly');
}
}

View File

@ -1,155 +0,0 @@
import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf';
import { OpenViduComponentsPO } from './utils.po.test';
const url = TestAppConfig.appUrl;
describe('Prejoin: Virtual Backgrounds', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should close BACKGROUNDS on prejoin page when VIDEO is disabled', async () => {
let element;
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
const backgroundButton = await utils.waitForElement('#backgrounds-button');
expect(await utils.isPresent('#backgrounds-button')).toBeTrue();
expect(await backgroundButton.isEnabled()).toBeTrue();
await utils.clickOn('#backgrounds-button');
await browser.sleep(500);
await utils.waitForElement('#background-effects-container');
expect(await utils.isPresent('#background-effects-container')).toBeTrue();
await utils.clickOn('#camera-button');
await browser.sleep(500);
element = await utils.waitForElement('#video-poster');
expect(await utils.isPresent('#video-poster')).toBeTrue();
expect(await backgroundButton.isDisplayed()).toBeTrue();
expect(await backgroundButton.isEnabled()).toBeFalse();
await browser.sleep(1000);
expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0);
});
it('should open and close BACKGROUNDS panel on prejoin page', async () => {
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
const backgroundButton = await utils.waitForElement('#backgrounds-button');
expect(await utils.isPresent('#backgrounds-button')).toBeTrue();
expect(await backgroundButton.isEnabled()).toBeTrue();
await backgroundButton.click();
await browser.sleep(500);
await utils.waitForElement('#background-effects-container');
expect(await utils.isPresent('#background-effects-container')).toBeTrue();
await utils.clickOn('#backgrounds-button');
await browser.sleep(1000);
expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0);
});
it('should apply a background effect on prejoin page', async () => {
await browser.get(`${url}`);
await utils.checkPrejoinIsPresent();
let videoElement = await utils.waitForElement('.OV_video-element');
await utils.saveScreenshot('before.png', videoElement);
await utils.applyVirtualBackgroundFromPrejoin('1');
await browser.sleep(1000);
videoElement = await utils.waitForElement('.OV_video-element');
await utils.saveScreenshot('after.png', videoElement);
await utils.expectVirtualBackgroundApplied('before.png', 'after.png');
});
});
describe('Room: Virtual Backgrounds', () => {
let browser: WebDriver;
let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> {
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 {
await utils.leaveRoom();
} catch (error) {}
await browser.quit();
});
it('should open and close BACKGROUNDS panel in the room', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.checkToolbarIsPresent();
await utils.togglePanel('backgrounds');
await utils.waitForElement('#background-effects-container');
expect(await utils.isPresent('#background-effects-container')).toBeTrue();
await utils.togglePanel('backgrounds');
await browser.sleep(1000);
expect(await utils.getNumberOfElements('#background-effects-container')).toBe(0);
});
it('should apply a background effect in the room', async () => {
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
await utils.togglePanel('backgrounds');
await utils.waitForElement('#background-effects-container');
expect(await utils.isPresent('#background-effects-container')).toBeTrue();
let videoElement = await utils.waitForElement('.OV_video-element');
await utils.saveScreenshot('before.png', videoElement);
await utils.applyBackground('1');
await browser.sleep(1000);
videoElement = await utils.waitForElement('.OV_video-element');
await utils.saveScreenshot('after.png', videoElement);
await utils.expectVirtualBackgroundApplied('before.png', 'after.png');
});
});

View File

@ -1,27 +0,0 @@
#!/bin/bash
if [[ -z "$BASEHREF_VERSION" ]]; then
echo "Example of use: \"BASEHREF_VERSION=2.12.0 ${0}\"" 1>&2
exit 1
fi
# Replace version from "stable" to the specified one in all TypeDoc links
grep -rl '/latest/' projects src | xargs sed -i -e 's|/latest/|/'${BASEHREF_VERSION}'/|g'
# Replace testapp README by openvidu-components-angular README
mv README.md README-testapp.md
cp ./projects/openvidu-components-angular/README.md .
# Generate Compodoc
npm run doc:build
# Return links to "stable" version
grep -rl '/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/'${BASEHREF_VERSION}'/|/latest/|g'
# Undo changes with READMEs
rm README.md
mv README-testapp.md README.md
# Clean previous docs from openvidu.io repo and copy new ones
npm run doc:clean-copy

View File

@ -1,75 +0,0 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--headless',
'--disable-gpu',
'--disable-translate',
'--disable-extensions',
// Without a remote debugging port, Google Chrome exits immediately.
'--no-sandbox',
'--remote-debugging-port=9222',
'--js-flags="--max_old_space_size=4096"'
]
},
Chrome: {
base: 'Chrome',
flags: [
'--disable-gpu',
'--disable-translate',
'--disable-extensions',
// Without a remote debugging port, Google Chrome exits immediately.
'--no-sandbox',
'--remote-debugging-port=9222',
'--js-flags="--max_old_space_size=4096"'
]
}
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true,
verbose: true,
thresholds: {
emitWarning: false,
global: {
statements: 80,
branches: 80,
functions: 80,
lines: 80
},
each: {
statements: 80,
branches: 80,
functions: 80,
lines: 80
}
}
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
singleRun: false,
restartOnFileChange: true
});
};

View File

@ -1,279 +0,0 @@
## OpenVidu Angular 3.0.0 Migration Guide
1. **Introduction**
Mejoras:
publicacion de multiples tracks por participante
Desventajas
Backgrounds no existe en openvidu 3.0.0
Captions no existe en openvidu 3.0.0
2. **Updating Dependencies**
OpenVidu Components 2.x was developed using Angular 14. Now, this version 3 is developed with Angular 15. This means that you will need to update your Angular version to 15.0.0 or higher. Check Angular Migration Guide for more information: https://update.angular.io/
Install the new version of OpenVidu Components:
```bash
npm install openvidu-components-angular@3.0.0
```
3. **Source Code Migration**
#### Components
- #### ActivitiesPanelComponent (`ov-activities-panel`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### BroadcastingActivityComponent (`ov-broadcasting-activity`):
##### Events / `@Output`
- `onBroadcastingStartRequested` (Describe onBroadcastingStartRequested event here)
- `onRecordingPlayClicked` (Describe onRecordingPlayClicked event here)
##### API Directives / `@Input`
- `broadcastingError` has been deleted
- #### ChatPanelComponent (`ov-chat-panel`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### LayoutComponent (`ov-layout`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### PanelComponent (`ov-panel`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### ParticipantPanelItemComponent (`ov-participant-panel-item`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### ParticipantsPanelComponent (`ov-participants-panel`):
##### Events / `@Output`
##### API Directives / `@Input`
- #### RecordingActivityComponent (`ov-recording-activity`):
##### Events / `@Output`
- `onRecordingStartRequested` (Describe onRecordingStartRequested event here)
- `onRecordingStopRequested` (Describe onRecordingStopRequested event here)
##### API Directives / `@Input`
- `recordingError` has been deleted
- `recordingsList` has been deleted
- #### StreamComponent (`ov-stream`):
##### Events / `@Output`
##### API Directives / `@Input`
- `settingsButton` has been replaced by `videoControls`
- `stream` input has been replaced by `track` input. Check the [Pipes](#pipes) section for more information.
- #### ToolbarComponent (`ov-toolbar`):
##### Events / `@Output`
- `backgroundEffectsButton` has been deleted **TODO: Try to add it again!!**
- `captionsButton` has been deleted
- `displaySessionName` has been renamed to `displayRoomName`
##### API Directives / `@Input`
- #### VideoconferenceComponent (`ov-videoconference`):
##### Events / `@Output`
- `onActivitiesPanelDeleteRecordingClicked` has been replaced by `onRecordingDeleteRequested`
- `onActivitiesPanelForceRecordingUpdate` has been deleted
- `onActivitiesPanelPlayRecordingClicked` has been replaced by `onRecordingPlayClicked`
- `onActivitiesPanelStartBroadcastingClicked` have been replaced by `onBroadcastingStartRequested`
- `onActivitiesPanelStartRecordingClicked` and `onToolbarStartRecordingClicked` has been replaced by `onRecordingStartRequested`
- `onActivitiesPanelStopBroadcastingClicked` and `onToolbarStopBroadcastingClicked` has been replaced by `onBroadcastingStopRequested`
- `onActivitiesPanelStopRecordingClicked` and `onToolbarStopRecordingClicked` has been replaced by `onRecordingStopRequested`
- **// TODO: langChanged**
- `onNodeCrashed` has been deleted
- `onSessionCreated` has been replaced by `onRoomCreated`
- `onToolbarActivitiesPanelButtonClicked` has been replaced by `onActivitiesPanelStatusChanged`
- `onToolbarCameraButtonClicked` has been replaced by `onVideoEnabledChanged`
- `onToolbarChatPanelButtonClicked` has been replaced by `onChatPanelStatusChanged`
- `onToolbarFullscreenButtonClicked` has been replaced by `onFullscreenEnabledChanged`
- `onToolbarLeaveButtonClicked` has been replaced by `onRoomDisconnected`
- `onToolbarMicrophoneButtonClicked` has been replaced by `onAudioEnabledChanged`
- `onToolbarParticipantsPanelButtonClicked` has been replaced by `onParticipantsPanelStatusChanged`
- `onToolbarScreenshareButtonClicked` has been replaced by `onScreenShareEnabledChanged`
- `onSettingsPanelStatusChanged` has been added to notify when the settings panel status has changed
- `onTokenRequested` has been added to notify when a token need to be created
- `onVideoDeviceChanged` has been added to notify when the video device has changed
- `onAudioDeviceChanged` has been added to notify when the audio device has changed
- `onRecordingDownloadClicked` has been added to notify when the user clicks on the download button of a recording
- `onActivitiesPanelPlayRecordingClicked` has been replaced by `onRecordingPlayClicked`
- #### Admin Login (`ov-admin-login`):
- `onLoginButtonClicked` returns an object with the username and password instead of a secret string
##### API Directives / `@Input`
- `audioMuted` has been replaced by `audioEnabled`
- `broadcastingActivityBroadcastingError` has been deleted
- `captionsLang` has been deleted
- `captionsLangOptions` has been deleted
- `recordingActivityRecordingError` has been deleted
- `recordingActivityRecordingsList` has been deleted
- `streamSettingsButton` has been replaced by `streamVideoControls`
- `toolbarBackgroundEffectsButton` has been deleted **TODO: Try to add it again!!**
- `toolbarCaptionsButton` has been deleted
- `toolbarDisplaySessionName` has been renamed to `toolbarDisplayRoomName`
- `videoMuted` has been replaced by `videoEnabled`
#### Web Component
Apply the same Outputs and Inputs changes as in the VideoconferenceComponent
#### Pipes
- `stream` has been replaced by `track`
In version 2.X of openvidu-angular, the **ParticipantStreamsPipe** (`streams`) was employed to extract streams from both the local participant and remote participants' lists. Its purpose was to inject these streams into the **StreamComponent** (`ov-stream`).
However, in the current version (3.0.0), **StreamComponent** now requires direct access to participant tracks. Consequently, the `streams` pipe has been replaced by the **RemoteParticipantTracksPipe** (`tracks`), exclusively used for remote participants' lists. Local participant tracks can now be obtained directly from the track accessor of the `ParticipantModel` class. This accessor and pipe return an array of [`ParticipantTrackPublication`](#/interfaces/ParticipantTrackPublication.html) objects, simplifying the handling of tracks.
##### In v2.X (using `streams` pipe):
```html
<div class="item" *ngFor="let stream of localParticipant | streams">
<ov-stream [stream]="stream"></ov-stream>
</div>
<div class="item" *ngFor="let stream of remoteParticipants | streams">
<ov-stream [stream]="stream"></ov-stream>
</div>
```
##### In v3.0.0 (using `tracks` pipe):
```html
<div *ngFor="let track of localParticipant.tracks">
<ov-stream [track]="track"></ov-stream>
</div>
<div *ngFor="let track of remoteParticipants | tracks">
<ov-stream [track]="track"></ov-stream>
</div>
```
#### Services
- ##### Broadcasting Service (`BroadcastingService`):
- `broadcastingStatusObs` observable now pushes an [BroadcastingStatusInfo](#/interfaces/BroadcastingStatusInfo.html) object instead of an `BroadcastingStatus` in every update.
- `startBroadcasting` method has been replaced by `setBroadcastingStarting`
- `stopBroadcasting` method has been replaced by `setBroadcastingStopping`
- `updateStatus` method has been deleted. Now the status is updated automatically when the status changes.
You can check the [BroadcastingService](#/services/BroadcastingService.html) documentation for more information.
- ##### OpenVidu Service (`OpenViduService`):
- `disconnect` method has been renamed to `disconnectRoom`
- `getRemoteConnections` method has been moved to [ParticipantService](#participant-service-participantservice) and renamed to `getRemoteParticipants`.
- `getSession` method has been renamed to `getRoom`.
- `isSessionConnected` method has been renamed to `isRoomConnected`.
- `publishCamera` and `unpublishCamera` methods have been moved to [ParticipantService](#participant-service-participantservice) and renamed to `setCameraEnabled`
- `publishScreen` and `unpublishScreen` methods have been renamed to `setScreenShareEnabled`
- `isSttReadyObs` observable has been deleted
- `connectRoom` method has been added
- `getRoomName` method has been added to get the name of the room
- ##### Panel Service (`PanelService`):
- `panelOpenedObs` observable has been renamed to `panelStatusObs` which returns a [PanelStatusInfo](#/interfaces/PanelStatusInfo.html) object instead of a `PanelEvent` in every update.
- `isBackgroundEffectsPanelOpened` method has been deleted
You can check the [PanelService](#/services/PanelService.html) documentation for more information.
- ##### Participant Service (`ParticipantService`):
- `amIModerator` method has been renamed to `amIRoomAdmin`
- `toggleScreenshare` method has been renamed to `switchScreenShare`
- `isMyVideoActive` method has been renamed to `isMyCameraEnabled`
- `isMyAudioActive` method has been renamed to `isMyMicrophoneEnabled`
- `publishVideo` method has been renamed to `setCameraEnabled`
- `publishAudio` method has been renamed to `setMicrophoneEnabled`
You can check the [ParticipantService](#/services/ParticipantService.html) documentation for more information.
- ##### Recording Service (`RecordingService`):
- `recordingStatusObs` observable now pushes an [RecordingStatusInfo](#/interfaces/RecordingStatusInfo.html) object instead of an `RecordingStatus` in every update.
- `forceUpdateRecordings` method has been deleted. Now the recordings are updated automatically when the status changes.
- `updateStatus` method has been deleted. Now the status is updated automatically when the status changes.
You can check the [RecordingService](#/services/RecordingService.html) documentation for more information.
#### Models
- `ParticipantAbstracModel` has been renamed to `ParticipantModel`. In addition, the `ParticipantModel` class has been refactored and now it has a new property called `tracks` that contains all the tracks of the participant. This property is an array of `ParticipantTrackPublication` objects.
- `StreamModel` has been renamed to `ParticipantTrackPublication`.
#### Interfaces
- `BroadcastingError` has been deleted
- `CaptionsLangOptions` has been deleted
- `PanelEvent` has been renamed to `PanelStatusInfo`
- `StreamModel` has been renamed to `ParticipantTrackPublication`
- `TokenModel` has been deleted
4. **References and Additional Resources**
5. **FAQ (Frequently Asked Questions)**
#### **Why should I migrate to version 3.0.0 of OpenVidu Angular?**
**TODO**
**Which versions of Angular are compatible with OpenVidu Angular 3.0.0?**
OpenVidu Angular 3.0.0 is compatible with Angular 15 and later versions. Make sure to update your Angular project to version 15 or higher to use this version of OpenVidu Angular.
#### **How can I check the current version of Angular in my project?**
You can check the current version of Angular in your project by running the following command in your terminal:
```
ng version
```
This will provide you with detailed information about the version of Angular you are using in your project.
#### **Where can I find examples of using the new features and API changes?**
You can find examples of using the new features and API changes in the official documentation of OpenVidu Angular 3.0.0. Visit the [official tutorials](#).
#### **How can I contribute or report issues with the new version?**
If you wish to contribute to the development of OpenVidu Angular or report issues, you can do so through the collaborative development platform, such as GitHub. Visit the official repository at [OpenVidu Angular repository](#) for information on how to contribute and how to report issues. Your participation is valuable in improving the library and assisting other users.
6. **Support and Community**

File diff suppressed because it is too large Load Diff

View File

@ -1,108 +0,0 @@
{
"dependencies": {
"@angular/animations": "19.2.8",
"@angular/cdk": "19.2.11",
"@angular/common": "19.2.8",
"@angular/core": "19.2.8",
"@angular/forms": "19.2.8",
"@angular/material": "19.2.11",
"@angular/platform-browser": "19.2.8",
"@angular/platform-browser-dynamic": "19.2.8",
"@angular/router": "19.2.8",
"@livekit/track-processors": "^0.5.6",
"@types/dom-mediacapture-transform": "^0.1.11",
"autolinker": "4.0.0",
"livekit-client": "2.11.4",
"rxjs": "7.8.1",
"tslib": "2.7.0",
"zone.js": "^0.15.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "19.2.9",
"@angular/cli": "19.2.9",
"@angular/compiler": "19.2.8",
"@angular/compiler-cli": "19.2.8",
"@compodoc/compodoc": "^1.1.25",
"@types/jasmine": "^5.1.4",
"@types/node": "20.12.14",
"@types/pngjs": "^6.0.5",
"@types/selenium-webdriver": "4.1.16",
"@types/ws": "^8.5.12",
"chromedriver": "138.0.0",
"concat": "^1.0.3",
"cross-env": "^7.0.3",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"http-server": "14.1.1",
"husky": "^9.1.6",
"jasmine": "^5.3.1",
"jasmine-core": "5.3.0",
"jasmine-spec-reporter": "7.0.0",
"karma": "^6.4.4",
"karma-chrome-launcher": "3.2.0",
"karma-coverage": "^2.2.1",
"karma-coverage-istanbul-reporter": "3.0.3",
"karma-jasmine": "5.1.0",
"karma-jasmine-html-reporter": "2.1.0",
"karma-junit-reporter": "2.0.1",
"karma-mocha-reporter": "2.2.5",
"karma-notify-reporter": "1.3.0",
"lint-staged": "^15.2.10",
"ng-packagr": "19.2.2",
"npm-watch": "^0.13.0",
"pixelmatch": "^7.1.0",
"pngjs": "^7.0.0",
"prettier": "3.3.3",
"selenium-webdriver": "4.32.0",
"ts-node": "10.9.2",
"tslint": "6.1.3",
"typescript": "5.8.3",
"webpack-bundle-analyzer": "^4.10.2"
},
"name": "openvidu-components-testapp",
"private": true,
"watch": {
"doc:serve": {
"patterns": [
"projects",
"src"
],
"extensions": "ts,html,scss,css,md",
"quiet": false
}
},
"scripts": {
"start": "ng serve --configuration development --open",
"start-prod": "npx http-server ./dist/openvidu-components-testapp/browser --port 4200",
"start:ssl": "ng serve --ssl --configuration development --host 0.0.0.0 --port 5080",
"build": "ng build openvidu-components-testapp --configuration production",
"doc:build": "npx compodoc -c ./projects/openvidu-components-angular/doc/.compodocrc.json",
"doc:generate-directives-tutorials": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tutorials.js",
"doc:generate-directive-tables": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tables.js",
"doc:clean-copy": "rm -rf ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular && cp -r ./docs/openvidu-components-angular/ ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular",
"doc:serve": "npx compodoc -c ../openvidu-components-angular/projects/openvidu-components-angular/doc/.compodocrc.json --serve --port 7000",
"doc:serve-watch": "npm-watch doc:serve",
"lib:serve": "ng build openvidu-components-angular --watch",
"lib:build": "ng build openvidu-components-angular --configuration production && cd ./dist/openvidu-components-angular",
"lib:pack": "cd ./dist/openvidu-components-angular && npm pack",
"lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call/frontend",
"lib:test": "ng test openvidu-components-angular --no-watch --code-coverage",
"e2e:nested-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/*.test.js",
"e2e:nested-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/events.test.js",
"e2e:nested-structural-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/structural-directives.test.js",
"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-internal-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/internal-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:lib-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/media-devices.test.js",
"e2e:lib-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/panels.test.js",
"e2e:lib-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/screensharing.test.js",
"e2e:lib-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/stream.test.js",
"e2e:lib-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/toolbar.test.js",
"e2e:lib-virtual-backgrounds": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/virtual-backgrounds.test.js",
"simulate:multiparty": "livekit-cli load-test --url ws://localhost:7880 --api-key devkey --api-secret secret --room daily-call --publishers 8 --audio-publishers 8 --identity-prefix Participant --identity publisher",
"husky": "cd .. && husky install"
},
"version": "3.3.0"
}

View File

@ -1,9 +0,0 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist

View File

@ -1,10 +0,0 @@
{
"singleQuote": true,
"printWidth": 140,
"trailingComma": "none",
"semi": true,
"bracketSpacing": true,
"useTabs": true,
"jsxSingleQuote": true,
"tabWidth": 4
}

View File

@ -1,99 +0,0 @@
# Welcome to OpenVidu Components Angular
Build powerful video conferencing applications with ease using OpenVidu Components Angular.
## Introduction
Angular Components are the simplest way to create real-time videoconferencing apps with Angular. There's no need to manage state or low-level events; Angular Components from OpenVidu handle all the complexity for you.
## Getting Started
To get started with OpenVidu Components Angular, visit our [**Getting Started guide**](https://openvidu.io/latest/docs/ui-components/angular-components/).
1. Create an Angular Project (>= 17.0.0)
```bash
ng new your-project-name
```
2. Add Angular Material to your project
```bash
ng add @angular/material
```
3. Install OpenVidu Components Angular
```bash
npm install openvidu-components-angular
```
4. Import and use OpenVidu Components Angular
```typescript
import { OpenViduComponentsModule, OpenViduComponentsConfig } from 'openvidu-components-angular';
// Other imports ...
const config: OpenViduComponentsConfig = {
production: environment.production
};
bootstrapApplication(AppComponent, {
providers: [
importProvidersFrom(
OpenViduComponentsModule.forRoot(config)
// Other imports ...
),
provideAnimations()
]
}).catch((err) => console.error(err));
```
You can also customize the styles in your `styles.scss` file:
```scss
:root {
/* Basic colors */
--ov-background-color: #303030; // Background color
--ov-surface-color: #ffffff; // Surfaces colors (panels, dialogs)
/* Text colors */
--ov-text-primary-color: #ffffff; // Text color over primary background
--ov-text-surface-color: #1d1d1d; // Text color over surface background
/* Action colors */
--ov-primary-action-color: #273235; // Primary color for buttons, etc.
--ov-secondary-action-color: #f1f1f1; // Secondary color for buttons, etc.
--ov-accent-action-color: #0089ab; // Color for highlighted elements
/* Status colors */
--ov-error-color: #eb5144; // Error color
--ov-warn-color: #ffba53; // Warning color
/* Radius */
--ov-toolbar-buttons-radius: 50%; // Radius for toolbar buttons
--ov-leave-button-radius: 10px; // Radius for leave button
--ov-video-radius: 5px; // Radius for video elements
--ov-surface-radius: 5px; // Radius for surface elements
}
```
## Usage
```html
<ov-videoconference
[token]="token"
[livekitUrl]="LIVEKIT_URL"
(onTokenRequested)="onTokenRequested($event)"
>
</ov-videoconference>
```
## API Documentation
For detailed information on OpenVidu Angular Components, refer to our [**API Reference**](https://openvidu.io/latest/docs/reference-docs/openvidu-components-angular/).
## Support
If you have any questions or need assistance, please reach out to our [**Support page**](https://openvidu.io/support/).

View File

@ -1,30 +0,0 @@
{
"name": "OpenVidu Components Angular Documentation",
"output": "./docs/openvidu-components-angular",
"hideGenerator": true,
"disableLifeCycleHooks": true,
"disableProtected": true,
"disableInternal": true,
"disablePrivate": true,
"disableCoverage": true,
"disableRoutesGraph": true,
"disableSourceCode": true,
"disableTemplateTab": true,
"disableDomTree": true,
"disableStyleTab": true,
"disableDependencies": true,
"theme": "gitbook",
"customFavicon": "./projects/openvidu-components-angular/doc/favicon.ico",
"extTheme": "./projects/openvidu-components-angular/doc/styles",
"navTabConfig": [
{
"id": "info",
"label": "Info"
},
{
"id": "readme",
"label": "Directives"
}
],
"tsconfig": "./projects/openvidu-components-angular/doc/tsconfig.doc.json"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,336 +0,0 @@
const fs = require('fs');
const glob = require('glob');
const startApiLine = '<!-- start-dynamic-api-directives-content -->';
const apiDirectivesTable =
'| **Parameter** | **Type** | **Reference** | \n' +
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
/**
* Get all directive files from the API directives directory
*/
function getDirectiveFiles() {
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
return listFiles(directivesDir, '.directive.ts');
}
/**
* Get all component files
*/
function getComponentFiles() {
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
return listFiles(componentsDir, '.component.ts');
}
/**
* Get all admin files
*/
function getAdminFiles() {
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
return listFiles(componentsDir, '.component.ts');
}
/**
* List all files with specific extension in directory
*/
function listFiles(directoryPath, fileExtension) {
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
if (files.length === 0) {
throw new Error(`No ${fileExtension} files found in ${directoryPath}`);
}
return files;
}
/**
* Extract component selector from component file
*/
function getComponentSelector(componentFile) {
const componentContent = fs.readFileSync(componentFile, 'utf8');
const selectorMatch = componentContent.match(/@Component\({[^]*?selector:\s*['"]([^'"]+)['"][^]*?}\)/s);
if (!selectorMatch) {
throw new Error(`Unable to find selector in component file: ${componentFile}`);
}
return selectorMatch[1];
}
/**
* Check if a directive class has @internal annotation
*/
function isInternalDirective(directiveContent, className) {
const classRegex = new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)?\\s*@Directive\\([\\s\\S]*?\\)\\s*export\\s+class\\s+${escapeRegex(className)}`, 'g');
const match = classRegex.exec(directiveContent);
if (match && match[1]) {
return match[1].includes('@internal');
}
return false;
}
/**
* Extract attribute name from selector for a specific component
*/
function extractAttributeForComponent(selector, componentSelector) {
// Split selector by comma and trim whitespace
const selectorParts = selector.split(',').map(part => part.trim());
// Find the part that matches our component
for (const part of selectorParts) {
if (part.includes(componentSelector)) {
// Extract attribute from this specific part
const attributeMatch = part.match(/\[([^\]]+)\]/);
if (attributeMatch) {
return attributeMatch[1];
}
}
}
// Fallback: if no specific match, return the first attribute found
const fallbackMatch = selector.match(/\[([^\]]+)\]/);
return fallbackMatch ? fallbackMatch[1] : null;
}
/**
* Extract all directive classes from a directive file
*/
function extractDirectiveClasses(directiveContent) {
const classes = [];
// Regex to find all directive class definitions with their preceding @Directive decorators
const directiveClassRegex = /@Directive\(\s*{\s*selector:\s*['"]([^'"]+)['"][^}]*}\s*\)\s*export\s+class\s+(\w+)/gs;
let match;
while ((match = directiveClassRegex.exec(directiveContent)) !== null) {
const selector = match[1];
const className = match[2];
// Skip internal directives
if (isInternalDirective(directiveContent, className)) {
console.log(`Skipping internal directive: ${className}`);
continue;
}
classes.push({
selector,
className
});
}
return classes;
}
/**
* Extract all directives from a directive file that match a component selector
*/
function extractDirectivesForComponent(directiveFile, componentSelector) {
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
const directives = [];
// Get all directive classes in the file (excluding internal ones)
const directiveClasses = extractDirectiveClasses(directiveContent);
// Filter classes that match the component selector
const matchingClasses = directiveClasses.filter(directiveClass =>
directiveClass.selector.includes(componentSelector)
);
// For each matching class, extract input type information
matchingClasses.forEach(directiveClass => {
// Extract the correct attribute name for this component
const attributeName = extractAttributeForComponent(directiveClass.selector, componentSelector);
if (attributeName) {
const inputInfo = extractInputInfo(directiveContent, attributeName, directiveClass.className);
if (inputInfo) {
directives.push({
attribute: attributeName,
type: inputInfo.type,
className: directiveClass.className
});
}
}
});
return directives;
}
/**
* Extract input information (type) for a specific attribute and class
*/
function extractInputInfo(directiveContent, attributeName, className) {
// Create a regex to find the specific class section
const classRegex = new RegExp(`export\\s+class\\s+${escapeRegex(className)}[^}]*?{([^]*?)(?=export\\s+class|$)`, 's');
const classMatch = directiveContent.match(classRegex);
if (!classMatch) {
console.warn(`Could not find class ${className}`);
return null;
}
const classContent = classMatch[1];
// Regex to find the @Input setter for this attribute within the class
const inputRegex = new RegExp(
`@Input\\(\\)\\s+set\\s+${escapeRegex(attributeName)}\\s*\\(\\s*\\w+:\\s*([^)]+)\\s*\\)`,
'g'
);
const inputMatch = inputRegex.exec(classContent);
if (!inputMatch) {
console.warn(`Could not find @Input setter for attribute: ${attributeName} in class: ${className}`);
return null;
}
let type = inputMatch[1].trim();
// Clean up the type (remove extra whitespace, etc.)
type = type.replace(/\s+/g, ' ');
return {
type: type
};
}
/**
* Escape special regex characters
*/
function escapeRegex(string) {
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
/**
* Generate API directives table for components
*/
function generateApiDirectivesTable(componentFiles, directiveFiles) {
componentFiles.forEach((componentFile) => {
try {
console.log(`Processing component: ${componentFile}`);
const componentSelector = getComponentSelector(componentFile);
const readmeFilePath = componentFile.replace('.ts', '.md');
console.log(`Component selector: ${componentSelector}`);
// Initialize table with header
initializeDynamicTableContent(readmeFilePath);
const allDirectives = [];
// Extract directives from all directive files
directiveFiles.forEach((directiveFile) => {
console.log(`Checking directive file: ${directiveFile}`);
const directives = extractDirectivesForComponent(directiveFile, componentSelector);
allDirectives.push(...directives);
});
console.log(`Found ${allDirectives.length} directives for ${componentSelector}`);
// Sort directives alphabetically by attribute name
allDirectives.sort((a, b) => a.attribute.localeCompare(b.attribute));
// Add rows to table
allDirectives.forEach((directive) => {
addRowToTable(readmeFilePath, directive.attribute, directive.type, directive.className);
});
// If no directives found, add "no directives" message
if (allDirectives.length === 0) {
removeApiTableContent(readmeFilePath);
}
} catch (error) {
console.error(`Error processing component ${componentFile}:`, error.message);
}
});
}
/**
* Initialize table with header
*/
function initializeDynamicTableContent(filePath) {
replaceDynamicTableContent(filePath, apiDirectivesTable);
}
/**
* Replace table content with "no directives" message
*/
function removeApiTableContent(filePath) {
const content = '_No API directives available for this component_. \n';
replaceDynamicTableContent(filePath, content);
}
/**
* Add a row to the markdown table
*/
function addRowToTable(filePath, parameter, type, reference) {
try {
const data = fs.readFileSync(filePath, 'utf8');
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
const lines = data.split('\n');
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
if (targetIndex !== -1) {
lines.splice(targetIndex, 0, markdownRow);
const updatedContent = lines.join('\n');
fs.writeFileSync(filePath, updatedContent, 'utf8');
console.log(`Added directive: ${parameter} -> ${reference}`);
} else {
console.error('End marker not found in file:', filePath);
}
} catch (error) {
console.error('Error adding row to table:', error);
}
}
/**
* Replace content between start and end markers
*/
function replaceDynamicTableContent(filePath, content) {
try {
const data = fs.readFileSync(filePath, 'utf8');
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
return startApiLine + '\n' + content + '\n' + endApiLine;
});
fs.writeFileSync(filePath, modifiedContent, 'utf8');
console.log(`Updated table content in: ${filePath}`);
} catch (error) {
if (error.code === 'ENOENT') {
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
} else {
console.error('Error writing to file:', error);
}
}
}
// Main execution
if (require.main === module) {
try {
const directiveFiles = getDirectiveFiles();
const componentFiles = getComponentFiles();
const adminFiles = getAdminFiles();
console.log('Starting directive table generation...');
generateApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
console.log('Directive table generation completed!');
} catch (error) {
console.error('Script execution failed:', error);
process.exit(1);
}
}
// Export functions for testing
module.exports = {
generateApiDirectivesTable,
getDirectiveFiles,
getComponentFiles,
getAdminFiles
};

Some files were not shown because too many files have changed in this diff Show More