Compare commits

..

No commits in common. "master" and "v2.11.0" have entirely different histories.

855 changed files with 93465 additions and 112301 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

5
.gitignore vendored
View File

@ -4,10 +4,8 @@
/target /target
.classpath .classpath
.idea
.project .project
.settings .settings
*.iml
*orig *orig
.springBeans .springBeans
*tmp/ *tmp/
@ -19,11 +17,8 @@ nbactions.xml
.externalToolBuilders .externalToolBuilders
*bin/ *bin/
*/.vscode/* */.vscode/*
.vscode/*
*/.sts4-cache/* */.sts4-cache/*
*/.project */.project
*/.classpath */.classpath
*/.settings/* */.settings/*
*/.tscache/* */.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

2
NOTICE
View File

@ -1,4 +1,4 @@
(C) Copyright 2017-2022 OpenVidu (https://openvidu.io) (C) Copyright 2017-2019 OpenVidu (https://openvidu.io)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,13 +1,7 @@
[![Backers on Open Collective](https://opencollective.com/openvidu/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/openvidu/sponsors/badge.svg)](#sponsors) [![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)
[![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)](https://openvidu.io/docs/home/)
[![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)
[![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) [![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/) [![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu)
[![Twitter Follow](https://img.shields.io/twitter/follow/openvidu.svg?style=social)](https://twitter.com/openvidu) [![Twitter Follow](https://img.shields.io/twitter/follow/openvidu.svg?style=social)](https://twitter.com/openvidu)
[![][OpenViduLogo]](https://openvidu.io) [![][OpenViduLogo]](https://openvidu.io)
@ -17,16 +11,12 @@ openvidu
Visit [openvidu.io](https://openvidu.io) 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 [OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
## Contributors ## Contributors
This project exists thanks to all the people who contribute. 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> <a href="https://github.com/undefined/undefinedgraphs/contributors"><img src="https://opencollective.com/openvidu/contributors.svg?width=890&button=false" /></a>
## Backers ## Backers
@ -35,11 +25,6 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
<a href="https://opencollective.com/openvidu#backers" target="_blank"><img src="https://opencollective.com/openvidu/backers.svg?width=890"></a> <a href="https://opencollective.com/openvidu#backers" target="_blank"><img src="https://opencollective.com/openvidu/backers.svg?width=890"></a>
## Acknowledgments
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.
<img height="75px" src="https://docs.openvidu.io/en/stable/img/logos/support.jpg">
## Sponsors ## Sponsors

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

@ -0,0 +1,61 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
docs/
lib/

View File

@ -0,0 +1 @@
docs/

View File

@ -0,0 +1,154 @@
{
"extends": "tslint:recommended",
"rules": {
"array-type": [
true,
"array"
],
"ban-types": {
"options": [
[
"Object",
"Avoid using the `Object` type. Did you mean `object`?"
],
[
"Function",
"Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`."
],
[
"Boolean",
"Avoid using the `Boolean` type. Did you mean `boolean`?"
],
[
"Number",
"Avoid using the `Number` type. Did you mean `number`?"
],
[
"String",
"Avoid using the `String` type. Did you mean `string`?"
]
]
},
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": [
true,
"ignore-same-line"
],
"indent": [
true,
"spaces",
2
],
"interface-name": [
true,
"never-prefix"
],
"interface-over-type-literal": true,
"jsdoc-format": true,
"no-inferrable-types": true,
"no-internal-module": true,
"no-null-keyword": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": [
true,
"ignore-template-strings"
],
"no-var-keyword": true,
"object-literal-shorthand": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single",
"avoid-escape",
"avoid-template"
],
"semicolon": [
true,
"always",
"ignore-bound-class-methods"
],
"space-within-parens": true,
"triple-equals": true,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-type"
],
"no-implicit-dependencies": [
true,
"dev"
],
"object-literal-key-quotes": [
true,
"consistent-as-needed"
],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"arrow-parens": false,
"arrow-return-shorthand": false,
"forin": false,
"member-access": false,
"no-conditional-assignment": false,
"no-console": false,
"no-debugger": false,
"no-empty-interface": false,
"no-eval": false,
"no-object-literal-type-assertion": false,
"no-shadowed-variable": false,
"no-submodule-imports": false,
"no-var-requires": false,
"ordered-imports": false,
"prefer-conditional-expression": false,
"radix": false,
"trailing-comma": false,
"align": false,
"eofline": false,
"max-line-length": false,
"no-consecutive-blank-lines": false,
"space-before-function-paren": false,
"ban-comma-operator": false,
"max-classes-per-file": false,
"member-ordering": false,
"no-angle-bracket-type-assertion": false,
"no-bitwise": false,
"no-namespace": false,
"no-reference": false,
"object-literal-sort-keys": false,
"one-variable-per-declaration": false,
"unified-signatures": false
}
}

View File

@ -0,0 +1,24 @@
module.exports = {
lib: [
"lib.dom.d.ts",
"lib.es5.d.ts",
"lib.es2015.promise.d.ts",
"lib.scripthost.d.ts"
],
mode: "file",
module: "commonjs",
name: "OpenVidu Browser",
target: "es5",
externalPattern: "node_modules",
exclude: [
"**/OpenViduInternal/Interfaces/Private/**",
"**/OpenViduInternal/WebRtcStats/WebRtcStats.ts",
"**/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts",
"**/OpenViduInternal/ScreenSharing/**",
"**/OpenViduInternal/KurentoUtils/**"
],
excludeExternals: true,
excludePrivate: true,
theme: "default",
readme: "none"
}

5108
openvidu-browser/npm-shrinkwrap.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,46 @@
{
"author": "OpenVidu",
"dependencies": {
"@types/node": "12.6.8",
"@types/platform": "1.3.2",
"freeice": "2.2.2",
"hark": "1.2.3",
"platform": "1.3.5",
"uuid": "3.3.2",
"wolfy87-eventemitter": "5.2.6"
},
"description": "OpenVidu Browser",
"devDependencies": {
"browserify": "16.3.0",
"grunt": "1.0.4",
"grunt-autoprefixer": "3.0.4",
"grunt-cli": "1.3.2",
"grunt-contrib-copy": "1.0.0",
"grunt-contrib-sass": "1.0.0",
"grunt-contrib-uglify": "4.0.1",
"grunt-contrib-watch": "1.1.0",
"grunt-string-replace": "1.3.1",
"grunt-ts": "6.0.0-beta.22",
"tsify": "4.0.1",
"tslint": "5.18.0",
"typedoc": "0.15.0",
"typescript": "3.5.3",
"uglify-js": "3.6.0"
},
"license": "Apache-2.0",
"main": "lib/index.js",
"name": "openvidu-browser",
"repository": {
"type": "git",
"url": "git://github.com/OpenVidu/openvidu"
},
"scripts": {
"browserify": "VERSION=${VERSION:-}; cd src && ../node_modules/browserify/bin/cmd.js Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/openvidu-browser-$VERSION.js -v",
"browserify-prod": "VERSION=${VERSION:-}; cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/uglify-js/bin/uglifyjs --source-map content=inline --output ../static/js/openvidu-browser-$VERSION.min.js",
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --lib dom,es5,es2015.promise,scripthost",
"docs": "./node_modules/typedoc/bin/typedoc --options ./config/typedoc.js --out ./docs ./src && rm -rf ../../openvidu.io/api/openvidu-browser/* && cp -R ./docs/. ../../openvidu.io/api/openvidu-browser",
"test": "echo \"Error: no test specified\" && exit 1"
},
"types": "lib/index.d.ts",
"version": "2.11.0"
}

View File

@ -0,0 +1,5 @@
import { OpenVidu } from './OpenVidu/OpenVidu';
if (window) {
window['OpenVidu'] = OpenVidu;
}

View File

@ -0,0 +1,169 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Session } from './Session';
import { Stream } from './Stream';
import { ConnectionOptions } from '../OpenViduInternal/Interfaces/Private/ConnectionOptions';
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
/**
* 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
*/
export class Connection {
/**
* Unique identifier of the connection
*/
connectionId: string;
/**
* Time when this connection was created in OpenVidu Server (UTC milliseconds)
*/
creationTime: number;
/**
* 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)
*/
data: string;
/**
* @hidden
*/
stream: Stream;
/**
* @hidden
*/
options: ConnectionOptions | undefined;
/**
* @hidden
*/
disposed = false;
/**
* @hidden
*/
rpcSessionId: string;
/**
* @hidden
*/
constructor(private session: Session, opts?: ConnectionOptions) {
let msg = "'Connection' created ";
if (!!opts) {
// Connection is remote
msg += "(remote) with 'connectionId' [" + opts.id + ']';
this.options = opts;
this.connectionId = opts.id;
this.creationTime = opts.createdAt;
if (opts.metadata) {
this.data = opts.metadata;
}
if (opts.streams) {
this.initRemoteStreams(opts.streams);
}
} else {
// Connection is local
msg += '(local)';
}
console.info(msg);
}
/* Hidden methods */
/**
* @hidden
*/
sendIceCandidate(candidate: RTCIceCandidate): void {
console.debug((!!this.stream.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' +
this.connectionId, candidate);
this.session.openvidu.sendRequest('onIceCandidate', {
endpointName: this.connectionId,
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
}, (error, response) => {
if (error) {
console.error('Error sending ICE candidate: '
+ JSON.stringify(error));
}
});
}
/**
* @hidden
*/
initRemoteStreams(options: StreamOptionsServer[]): void {
// This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
// this.stream should also be replaced by a collection of streams to support multiple streams per Connection
options.forEach(opts => {
const streamOptions: InboundStreamOptions = {
id: opts.id,
createdAt: opts.createdAt,
connection: this,
hasAudio: opts.hasAudio,
hasVideo: opts.hasVideo,
audioActive: opts.audioActive,
videoActive: opts.videoActive,
typeOfVideo: opts.typeOfVideo,
frameRate: opts.frameRate,
videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined,
filter: !!opts.filter ? opts.filter : undefined
};
const stream = new Stream(this.session, streamOptions);
this.addStream(stream);
});
console.info("Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ', this.stream.inboundStreamOpts);
}
/**
* @hidden
*/
addStream(stream: Stream): void {
stream.connection = this;
this.stream = stream;
}
/**
* @hidden
*/
removeStream(streamId: string): void {
delete this.stream;
}
/**
* @hidden
*/
dispose(): void {
if (!!this.stream) {
delete this.stream;
}
this.disposed = true;
}
}

View File

@ -0,0 +1,191 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Stream } from './Stream';
import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { ObjMap } from '../OpenViduInternal/Interfaces/Private/ObjMap';
/**
* **WARNING**: experimental option. This interface may change in the near future
*
* Video/audio filter applied to a Stream. See [[Stream.applyFilter]]
*/
export class Filter {
/**
* Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server.
* For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`.
*
* You can get this property in `*.kmd.json` files defining the Kurento filters. For example, for GStreamerFilter that's
* [here](https://github.com/Kurento/kms-filters/blob/53a452fac71d61795952e3d2202156c6b00f6d65/src/server/interface/filters.GStreamerFilter.kmd.json#L4)
*/
type: string;
/**
* Parameters used to initialize the filter.
* These correspond to the constructor parameters used in the filter in Kurento Media Server (except for `mediaPipeline` parameter, which is never needed).
*
* For example: for `filter.type = "GStreamerFilter"` could be `filter.options = {"command": "videobalance saturation=0.0"}`
*
* You can get this property in `*.kmd.json` files defining the Kurento filters. For example, for GStreamerFilter that's
* [here](https://github.com/Kurento/kms-filters/blob/53a452fac71d61795952e3d2202156c6b00f6d65/src/server/interface/filters.GStreamerFilter.kmd.json#L13-L31)
*/
options: Object;
/**
* Value passed the last time [[Filter.execMethod]] was called. If `undefined` this method has not been called yet.
*
* You can use this value to know the current status of any applied filter
*/
lastExecMethod?: {
method: string, params: Object
};
/**
* @hidden
*/
handlers: ObjMap<(event: FilterEvent) => void> = {};
/**
* @hidden
*/
stream: Stream;
/**
* @hidden
*/
constructor(type: string, options: Object) {
this.type = type;
this.options = options;
}
/**
* Executes a filter method. Available methods are specific for each filter
*
* @param method Name of the method
* @param params Parameters of the method
*/
execMethod(method: string, params: Object): Promise<any> {
return new Promise((resolve, reject) => {
console.info('Executing filter method to stream ' + this.stream.streamId);
let stringParams;
if (typeof params !== 'string') {
try {
stringParams = JSON.stringify(params);
} catch (error) {
const errorMsg = "'params' property must be a JSON formatted object";
console.error(errorMsg);
reject(errorMsg);
}
} else {
stringParams = <string>params;
}
this.stream.session.openvidu.sendRequest(
'execFilterMethod',
{ streamId: this.stream.streamId, method, params: stringParams },
(error, response) => {
if (error) {
console.error('Error executing filter method for Stream ' + this.stream.streamId, error);
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to execute a filter method"));
} else {
reject(error);
}
} else {
console.info('Filter method successfully executed on Stream ' + this.stream.streamId);
const oldValue = (<any>Object).assign({}, this.stream.filter);
this.stream.filter.lastExecMethod = { method, params: JSON.parse(stringParams) };
this.stream.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.session, this.stream, 'filter', this.stream.filter, oldValue, 'execFilterMethod')]);
this.stream.streamManager.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.stream.streamManager, this.stream, 'filter', this.stream.filter, oldValue, 'execFilterMethod')]);
resolve();
}
}
);
});
}
/**
* Subscribe to certain filter event. Available events are specific for each filter
*
* @param eventType Event to which subscribe to.
* @param handler Function to execute upon event dispatched. It receives as parameter a [[FilterEvent]] object
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully attached to the filter and rejected with an Error object if not
*/
addEventListener(eventType: string, handler: (event: FilterEvent) => void): Promise<any> {
return new Promise((resolve, reject) => {
console.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest(
'addFilterEventListener',
{ streamId: this.stream.streamId, eventType },
(error, response) => {
if (error) {
console.error('Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error);
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener"));
} else {
reject(error);
}
} else {
this.handlers[eventType] = handler;
console.info('Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId);
resolve();
}
}
);
});
}
/**
* Removes certain filter event listener previously set.
*
* @param eventType Event to unsubscribe from.
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully removed from the filter and rejected with an Error object in other case
*/
removeEventListener(eventType: string): Promise<any> {
return new Promise((resolve, reject) => {
console.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest(
'removeFilterEventListener',
{ streamId: this.stream.streamId, eventType },
(error, response) => {
if (error) {
console.error('Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId, error);
if (error.code === 401) {
reject(new OpenViduError(OpenViduErrorName.OPENVIDU_PERMISSION_DENIED, "You don't have permissions to add a filter event listener"));
} else {
reject(error);
}
} else {
delete this.handlers[eventType];
console.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId);
resolve();
}
}
);
});
}
}

View File

@ -0,0 +1,374 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Stream } from './Stream';
import { LocalRecorderState } from '../OpenViduInternal/Enums/LocalRecorderState';
import platform = require('platform');
/**
* @hidden
*/
declare var MediaRecorder: any;
/**
* Easy recording of [[Stream]] objects straightaway from the browser. Initialized with [[OpenVidu.initLocalRecorder]] method
*
* > WARNINGS:
* - Performing browser local recording of **remote streams** may cause some troubles. A long waiting time may be required after calling _LocalRecorder.stop()_ in this case
* - Only Chrome and Firefox support local stream recording
*/
export class LocalRecorder {
state: LocalRecorderState;
private connectionId: string;
private mediaRecorder: any;
private chunks: any[] = [];
private blob: Blob;
private id: string;
private videoPreviewSrc: string;
private videoPreview: HTMLVideoElement;
/**
* @hidden
*/
constructor(private stream: Stream) {
this.connectionId = (!!this.stream.connection) ? this.stream.connection.connectionId : 'default-connection';
this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord';
this.state = LocalRecorderState.READY;
}
/**
* Starts the recording of the Stream. [[state]] property must be `READY`. After method succeeds is set to `RECORDING`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully started and rejected with an Error object if not
*/
record(): Promise<any> {
return new Promise((resolve, reject) => {
try {
if (typeof MediaRecorder === 'undefined') {
console.error('MediaRecorder not supported on your browser. See compatibility in https://caniuse.com/#search=MediaRecorder');
throw (Error('MediaRecorder not supported on your browser. See compatibility in https://caniuse.com/#search=MediaRecorder'));
}
if (this.state !== LocalRecorderState.READY) {
throw (Error('\'LocalRecord.record()\' needs \'LocalRecord.state\' to be \'READY\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.clean()\' or init a new LocalRecorder before'));
}
console.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
if (typeof MediaRecorder.isTypeSupported === 'function') {
let options;
if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) {
options = { mimeType: 'video/webm;codecs=vp9' };
} else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) {
options = { mimeType: 'video/webm;codecs=h264' };
} else if (MediaRecorder.isTypeSupported('video/webm;codecs=vp8')) {
options = { mimeType: 'video/webm;codecs=vp8' };
}
console.log('Using mimeType ' + options.mimeType);
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
} else {
console.warn('isTypeSupported is not supported, using default codecs for browser');
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream());
}
this.mediaRecorder.start(10);
} catch (err) {
reject(err);
}
this.mediaRecorder.ondataavailable = (e) => {
this.chunks.push(e.data);
};
this.mediaRecorder.onerror = (e) => {
console.error('MediaRecorder error: ', e);
};
this.mediaRecorder.onstart = () => {
console.log('MediaRecorder started (state=' + this.mediaRecorder.state + ')');
};
this.mediaRecorder.onstop = () => {
this.onStopDefault();
};
this.mediaRecorder.onpause = () => {
console.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ')');
};
this.mediaRecorder.onresume = () => {
console.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ')');
};
this.mediaRecorder.onwarning = (e) => {
console.log('MediaRecorder warning: ' + e);
};
this.state = LocalRecorderState.RECORDING;
resolve();
});
}
/**
* Ends the recording of the Stream. [[state]] property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
*/
stop(): Promise<any> {
return new Promise((resolve, reject) => {
try {
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.stop()\' needs \'LocalRecord.state\' to be \'RECORDING\' or \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' before'));
}
this.mediaRecorder.onstop = () => {
this.onStopDefault();
resolve();
};
this.mediaRecorder.stop();
} catch (e) {
reject(e);
}
});
}
/**
* Pauses the recording of the Stream. [[state]] property must be `RECORDING`. After method succeeds is set to `PAUSED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
*/
pause(): Promise<any> {
return new Promise((resolve, reject) => {
try {
if (this.state !== LocalRecorderState.RECORDING) {
reject(Error('\'LocalRecord.pause()\' needs \'LocalRecord.state\' to be \'RECORDING\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.start()\' or \'LocalRecorder.resume()\' before'));
}
this.mediaRecorder.pause();
this.state = LocalRecorderState.PAUSED;
} catch (error) {
reject(error);
}
});
}
/**
* Resumes the recording of the Stream. [[state]] property must be `PAUSED`. After method succeeds is set to `RECORDING`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully resumed and rejected with an Error object if not
*/
resume(): Promise<any> {
return new Promise((resolve, reject) => {
try {
if (this.state !== LocalRecorderState.PAUSED) {
throw (Error('\'LocalRecord.resume()\' needs \'LocalRecord.state\' to be \'PAUSED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.pause()\' before'));
}
this.mediaRecorder.resume();
this.state = LocalRecorderState.RECORDING;
} catch (error) {
reject(error);
}
});
}
/**
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. [[state]] property must be `FINISHED`
*/
preview(parentElement): HTMLVideoElement {
if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.preview()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
}
this.videoPreview = document.createElement('video');
this.videoPreview.id = this.id;
this.videoPreview.autoplay = true;
if (platform.name === 'Safari') {
this.videoPreview.setAttribute('playsinline', 'true');
}
if (typeof parentElement === 'string') {
const parentElementDom = document.getElementById(parentElement);
if (parentElementDom) {
this.videoPreview = parentElementDom.appendChild(this.videoPreview);
}
} else {
this.videoPreview = parentElement.appendChild(this.videoPreview);
}
this.videoPreview.src = this.videoPreviewSrc;
return this.videoPreview;
}
/**
* Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets [[state]] to `READY` so the recording can start again
*/
clean(): void {
const f = () => {
delete this.blob;
this.chunks = [];
delete this.mediaRecorder;
this.state = LocalRecorderState.READY;
};
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
this.stop().then(() => f()).catch(() => f());
} else {
f();
}
}
/**
* Downloads the recorded video through the browser. [[state]] property must be `FINISHED`
*/
download(): void {
if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('\'LocalRecord.download()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
const a: HTMLAnchorElement = document.createElement('a');
a.style.display = 'none';
document.body.appendChild(a);
const url = window.URL.createObjectURL(this.blob);
a.href = url;
a.download = this.id + '.webm';
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
}
/**
* Gets the raw Blob file. Methods preview, download, uploadAsBinary and uploadAsMultipartfile use this same file to perform their specific actions. [[state]] property must be `FINISHED`
*/
getBlob(): Blob {
if (this.state !== LocalRecorderState.FINISHED) {
throw (Error('Call \'LocalRecord.stop()\' before getting Blob file'));
} else {
return this.blob;
}
}
/**
* Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ```
* var headers = {
* "Cookie": "$Version=1; Skin=new;",
* "Authorization":"Basic QWxhZGpbjpuIHNlctZQ=="
* }
* ```
* @returns A Promise (to which you can optionally subscribe to) that is resolved with the `http.responseText` from server if the operation was successful and rejected with the failed `http.status` if not
*/
uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) {
reject(Error('\'LocalRecord.uploadAsBinary()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
const http = new XMLHttpRequest();
http.open('POST', endpoint, true);
if (typeof headers === 'object') {
for (const key of Object.keys(headers)) {
http.setRequestHeader(key, headers[key]);
}
}
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status.toString().charAt(0) === '2') {
// Success response from server (HTTP status standard: 2XX is success)
resolve(http.responseText);
} else {
reject(http.status);
}
}
};
http.send(this.blob);
}
});
}
/**
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. [[state]] property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ```
* var headers = {
* "Cookie": "$Version=1; Skin=new;",
* "Authorization":"Basic QWxhZGpbjpuIHNlctZQ=="
* }
* ```
* @returns A Promise (to which you can optionally subscribe to) that is resolved with the `http.responseText` from server if the operation was successful and rejected with the failed `http.status` if not:
*/
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) {
reject(Error('\'LocalRecord.uploadAsMultipartfile()\' needs \'LocalRecord.state\' to be \'FINISHED\' (current value: \'' + this.state + '\'). Call \'LocalRecorder.stop()\' before'));
} else {
const http = new XMLHttpRequest();
http.open('POST', endpoint, true);
if (typeof headers === 'object') {
for (const key of Object.keys(headers)) {
http.setRequestHeader(key, headers[key]);
}
}
const sendable = new FormData();
sendable.append('file', this.blob, this.id + '.webm');
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status.toString().charAt(0) === '2') {
// Success response from server (HTTP status standard: 2XX is success)
resolve(http.responseText);
} else {
reject(http.status);
}
}
};
http.send(sendable);
}
});
}
/* Private methods */
private onStopDefault(): void {
console.log('MediaRecorder stopped (state=' + this.mediaRecorder.state + ')');
this.blob = new Blob(this.chunks, { type: 'video/webm' });
this.chunks = [];
this.videoPreviewSrc = window.URL.createObjectURL(this.blob);
this.state = LocalRecorderState.FINISHED;
}
}

View File

@ -0,0 +1,857 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { LocalRecorder } from './LocalRecorder';
import { Publisher } from './Publisher';
import { Session } from './Session';
import { Stream } from './Stream';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { Device } from '../OpenViduInternal/Interfaces/Public/Device';
import { OpenViduAdvancedConfiguration } from '../OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import * as screenSharingAuto from '../OpenViduInternal/ScreenSharing/Screen-Capturing-Auto';
import * as screenSharing from '../OpenViduInternal/ScreenSharing/Screen-Capturing';
import EventEmitter = require('wolfy87-eventemitter');
import RpcBuilder = require('../OpenViduInternal/KurentoUtils/kurento-jsonrpc');
import platform = require('platform');
platform['isIonicIos'] = (platform.product === 'iPhone' || platform.product === 'iPad') && platform.ua!!.indexOf('Safari') === -1;
platform['isIonicAndroid'] = platform.os!!.family === 'Android' && platform.name == "Android Browser";
/**
* @hidden
*/
const packageJson = require('../../package.json');
/**
* @hidden
*/
declare var cordova: any;
/**
* Entrypoint of OpenVidu Browser library.
* Use it to initialize objects of type [[Session]], [[Publisher]] and [[LocalRecorder]]
*/
export class OpenVidu {
private jsonRpcClient: any;
/**
* @hidden
*/
session: Session;
/**
* @hidden
*/
publishers: Publisher[] = [];
/**
* @hidden
*/
wsUri: string;
/**
* @hidden
*/
httpUri: string;
/**
* @hidden
*/
secret = '';
/**
* @hidden
*/
recorder = false;
/**
* @hidden
*/
iceServers: RTCIceServer[];
/**
* @hidden
*/
role: string;
/**
* @hidden
*/
advancedConfiguration: OpenViduAdvancedConfiguration = {};
/**
* @hidden
*/
webrtcStatsInterval: number = 0;
/**
* @hidden
*/
libraryVersion: string;
/**
* @hidden
*/
ee = new EventEmitter()
constructor() {
this.libraryVersion = packageJson.version;
console.info("'OpenVidu' initialized");
console.info("openvidu-browser version: " + this.libraryVersion);
if (platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') {
// Listen to orientationchange only on mobile devices
(<any>window).addEventListener('orientationchange', () => {
this.publishers.forEach(publisher => {
if (publisher.stream.isLocalStreamPublished && !!publisher.stream && !!publisher.stream.hasVideo && !!publisher.stream.streamManager.videos[0]) {
let attempts = 0;
const oldWidth = publisher.stream.videoDimensions.width;
const oldHeight = publisher.stream.videoDimensions.height;
const getNewVideoDimensions = (): Promise<{ newWidth: number, newHeight: number }> => {
return new Promise((resolve, reject) => {
if (platform['isIonicIos']) {
// iOS Ionic. Limitation: must get new dimensions from an existing video element already inserted into DOM
resolve({
newWidth: publisher.stream.streamManager.videos[0].video.videoWidth,
newHeight: publisher.stream.streamManager.videos[0].video.videoHeight
});
} else {
// Rest of platforms
// New resolution got from different places for Chrome and Firefox. Chrome needs a videoWidth and videoHeight of a videoElement.
// Firefox needs getSettings from the videoTrack
const firefoxSettings = publisher.stream.getMediaStream().getVideoTracks()[0].getSettings();
const newWidth = <number>((platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.width : publisher.videoReference.videoWidth);
const newHeight = <number>((platform.name!!.toLowerCase().indexOf('firefox') !== -1) ? firefoxSettings.height : publisher.videoReference.videoHeight);
resolve({ newWidth, newHeight });
}
});
};
const repeatUntilChange = setInterval(() => {
getNewVideoDimensions().then(newDimensions => {
sendStreamPropertyChangedEvent(oldWidth, oldHeight, newDimensions.newWidth, newDimensions.newHeight);
});
}, 75);
const sendStreamPropertyChangedEvent = (oldWidth, oldHeight, newWidth, newHeight) => {
attempts++;
if (attempts > 10) {
clearTimeout(repeatUntilChange);
}
if (newWidth !== oldWidth || newHeight !== oldHeight) {
publisher.stream.videoDimensions = {
width: newWidth || 0,
height: newHeight || 0
};
this.sendRequest(
'streamPropertyChanged',
{
streamId: publisher.stream.streamId,
property: 'videoDimensions',
newValue: JSON.stringify(publisher.stream.videoDimensions),
reason: 'deviceRotated'
},
(error, response) => {
if (error) {
console.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, 'deviceRotated')]);
publisher.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(publisher, publisher.stream, 'videoDimensions', publisher.stream.videoDimensions, { width: oldWidth, height: oldHeight }, 'deviceRotated')]);
}
});
clearTimeout(repeatUntilChange);
}
};
}
});
});
}
}
/**
* Returns new session
*/
initSession(): Session {
this.session = new Session(this);
return this.session;
}
initPublisher(targetElement: string | HTMLElement): Publisher;
initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties): Publisher;
initPublisher(targetElement: string | HTMLElement, completionHandler: (error: Error | undefined) => void): Publisher;
initPublisher(targetElement: string | HTMLElement, properties: PublisherProperties, completionHandler: (error: Error | undefined) => void): Publisher;
/**
* Returns a new publisher
*
* #### Events dispatched
*
* The [[Publisher]] object will dispatch an `accessDialogOpened` event, only if the pop-up shown by the browser to request permissions for the camera is opened. You can use this event to alert the user about granting permissions
* for your website. An `accessDialogClosed` event will also be dispatched after user clicks on "Allow" or "Block" in the pop-up.
*
* The [[Publisher]] object will dispatch an `accessAllowed` or `accessDenied` event once it has been granted access to the requested input devices or not.
*
* The [[Publisher]] object will dispatch a `videoElementCreated` event once a HTML video element has been added to DOM (only if you
* [let OpenVidu take care of the video players](/docs/how-do-i/manage-videos/#let-openvidu-take-care-of-the-video-players)). See [[VideoElementEvent]] to learn more.
*
* The [[Publisher]] object will dispatch a `streamPlaying` event once the local streams starts playing. See [[StreamManagerEvent]] to learn more.
*
* @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher will be inserted (see [[PublisherProperties.insertMode]]). If *null* or *undefined* no default video will be created for this Publisher.
* You can always call method [[Publisher.addVideoElement]] or [[Publisher.createVideoElement]] to manage the video elements on your own (see [Manage video players](/docs/how-do-i/manage-videos) section)
* @param completionHandler `error` parameter is null if `initPublisher` succeeds, and is defined if it fails.
* `completionHandler` function is called before the Publisher dispatches an `accessAllowed` or an `accessDenied` event
*/
initPublisher(targetElement: string | HTMLElement, param2?, param3?): Publisher {
let properties: PublisherProperties;
if (!!param2 && (typeof param2 !== 'function')) {
// Matches 'initPublisher(targetElement, properties)' or 'initPublisher(targetElement, properties, completionHandler)'
properties = (<PublisherProperties>param2);
properties = {
audioSource: (typeof properties.audioSource !== 'undefined') ? properties.audioSource : undefined,
frameRate: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.frameRate !== 'undefined') ? properties.frameRate : undefined),
insertMode: (typeof properties.insertMode !== 'undefined') ? ((typeof properties.insertMode === 'string') ? VideoInsertMode[properties.insertMode] : properties.insertMode) : VideoInsertMode.APPEND,
mirror: (typeof properties.mirror !== 'undefined') ? properties.mirror : true,
publishAudio: (typeof properties.publishAudio !== 'undefined') ? properties.publishAudio : true,
publishVideo: (typeof properties.publishVideo !== 'undefined') ? properties.publishVideo : true,
resolution: (typeof MediaStreamTrack !== 'undefined' && properties.videoSource instanceof MediaStreamTrack) ? undefined : ((typeof properties.resolution !== 'undefined') ? properties.resolution : '640x480'),
videoSource: (typeof properties.videoSource !== 'undefined') ? properties.videoSource : undefined,
filter: properties.filter
};
} else {
// Matches 'initPublisher(targetElement)' or 'initPublisher(targetElement, completionHandler)'
properties = {
insertMode: VideoInsertMode.APPEND,
mirror: true,
publishAudio: true,
publishVideo: true,
resolution: '640x480'
};
}
const publisher: Publisher = new Publisher(targetElement, properties, this);
let completionHandler: (error: Error | undefined) => void;
if (!!param2 && (typeof param2 === 'function')) {
completionHandler = param2;
} else if (!!param3) {
completionHandler = param3;
}
publisher.initialize()
.then(() => {
if (completionHandler !== undefined) {
completionHandler(undefined);
}
publisher.emitEvent('accessAllowed', []);
}).catch((error) => {
if (completionHandler !== undefined) {
completionHandler(error);
}
publisher.emitEvent('accessDenied', [error]);
});
this.publishers.push(publisher);
return publisher;
}
/**
* Promisified version of [[OpenVidu.initPublisher]]
*
* > WARNING: events `accessDialogOpened` and `accessDialogClosed` will not be dispatched if using this method instead of [[OpenVidu.initPublisher]]
*/
initPublisherAsync(targetElement: string | HTMLElement): Promise<Publisher>;
initPublisherAsync(targetElement: string | HTMLElement, properties: PublisherProperties): Promise<Publisher>;
initPublisherAsync(targetElement: string | HTMLElement, properties?: PublisherProperties): Promise<Publisher> {
return new Promise<Publisher>((resolve, reject) => {
let publisher: Publisher;
const callback = (error: Error) => {
if (!!error) {
reject(error);
} else {
resolve(publisher);
}
};
if (!!properties) {
publisher = this.initPublisher(targetElement, properties, callback);
} else {
publisher = this.initPublisher(targetElement, callback);
}
});
}
/**
* Returns a new local recorder for recording streams straight away from the browser
* @param stream Stream to record
*/
initLocalRecorder(stream: Stream): LocalRecorder {
return new LocalRecorder(stream);
}
/**
* Checks if the browser supports OpenVidu
* @returns 1 if the browser supports OpenVidu, 0 otherwise
*/
checkSystemRequirements(): number {
const browser = platform.name;
const family = platform.os!!.family;
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
// Reject iPhones and iPads if not Safari ('Safari' also covers Ionic for iOS)
if (family === 'iOS' && (browser !== 'Safari' || userAgent.indexOf('CriOS') !== -1 || userAgent.indexOf('FxiOS') !== -1)) {
return 0;
}
// Accept: Chrome (desktop and Android), Firefox (desktop and Android), Opera (desktop and Android),
// Safari (OSX and iOS), Ionic (Android and iOS)
if (
(browser !== 'Safari') &&
(browser !== 'Chrome') && (browser !== 'Chrome Mobile') &&
(browser !== 'Firefox') && (browser !== 'Firefox Mobile') &&
(browser !== 'Opera') && (browser !== 'Opera Mobile') &&
(browser !== 'Android Browser') && (browser !== 'Electron')
) {
return 0;
} else {
return 1;
}
}
/**
* Checks if the browser supports screen-sharing. Desktop Chrome, Firefox and Opera support screen-sharing
* @returns 1 if the browser supports screen-sharing, 0 otherwise
*/
checkScreenSharingCapabilities(): number {
const browser = platform.name;
const family = platform.os!!.family;
// Reject mobile devices
if (family === 'iOS' || family === 'Android') {
return 0;
}
if ((browser !== 'Chrome') && (browser !== 'Firefox') && (browser !== 'Opera') && (browser !== 'Electron')) {
return 0;
} else {
return 1;
}
}
/**
* Collects information about the media input devices available on the system. You can pass property `deviceId` of a [[Device]] object as value of `audioSource` or `videoSource` properties in [[initPublisher]] method
*/
getDevices(): Promise<Device[]> {
return new Promise<Device[]>((resolve, reject) => {
navigator.mediaDevices.enumerateDevices().then((deviceInfos) => {
const devices: Device[] = [];
// Ionic Android devices
if (platform['isIonicAndroid'] && cordova.plugins && cordova.plugins.EnumerateDevicesPlugin) {
cordova.plugins.EnumerateDevicesPlugin.getEnumerateDevices().then((pluginDevices: Device[]) => {
let pluginAudioDevices: Device[] = [];
let videoDevices: Device[] = [];
let audioDevices: Device[] = [];
pluginAudioDevices = pluginDevices.filter((device: Device) => device.kind === 'audioinput');
videoDevices = deviceInfos.filter((device: Device) => device.kind === 'videoinput');
audioDevices = deviceInfos.filter((device: Device) => device.kind === 'audioinput');
videoDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) {
let label = "";
if (index === 0) {
label = "Front Camera";
} else if (index === 1) {
label = "Back Camera";
} else {
label = "Unknown Camera";
}
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: label
});
} else {
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: deviceInfo.label
});
}
});
audioDevices.forEach((deviceInfo, index) => {
if (!deviceInfo.label) {
let label = "";
switch (index) {
case 0: // Default Microphone
label = 'Default';
break;
case 1: // Microphone + Speakerphone
const defaultMatch = pluginAudioDevices.filter((d) => d.label.includes('Built'))[0];
label = defaultMatch ? defaultMatch.label : 'Built-in Microphone';
break;
case 2: // Headset Microphone
const wiredMatch = pluginAudioDevices.filter((d) => d.label.includes('Wired'))[0];
if (wiredMatch) {
label = wiredMatch.label;
} else {
label = 'Headset earpiece';
}
break;
case 3:
const wirelessMatch = pluginAudioDevices.filter((d) => d.label.includes('Bluetooth'))[0];
label = wirelessMatch ? wirelessMatch.label : 'Wireless';
break;
default:
label = "Unknown Microphone";
break;
}
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: label
});
} else {
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: deviceInfo.label
});
}
});
resolve(devices);
});
} else {
// Rest of platforms
deviceInfos.forEach(deviceInfo => {
if (deviceInfo.kind === 'audioinput' || deviceInfo.kind === 'videoinput') {
devices.push({
kind: deviceInfo.kind,
deviceId: deviceInfo.deviceId,
label: deviceInfo.label
});
}
});
resolve(devices);
}
}).catch((error) => {
console.error('Error getting devices', error);
reject(error);
});
});
}
/**
* Get a MediaStream object that you can customize before calling [[initPublisher]] (pass _MediaStreamTrack_ property of the _MediaStream_ value resolved by the Promise as `audioSource` or `videoSource` properties in [[initPublisher]])
*
* Parameter `options` is the same as in [[initPublisher]] second parameter (of type [[PublisherProperties]]), but only the following properties will be applied: `audioSource`, `videoSource`, `frameRate`, `resolution`
*
* To customize the Publisher's video, the API for HTMLCanvasElement is very useful. For example, to get a black-and-white video at 10 fps and HD resolution with no sound:
* ```
* var OV = new OpenVidu();
* var FRAME_RATE = 10;
*
* OV.getUserMedia({
* audioSource: false;
* videoSource: undefined,
* resolution: '1280x720',
* frameRate: FRAME_RATE
* })
* .then(mediaStream => {
*
* var videoTrack = mediaStream.getVideoTracks()[0];
* var video = document.createElement('video');
* video.srcObject = new MediaStream([videoTrack]);
*
* var canvas = document.createElement('canvas');
* var ctx = canvas.getContext('2d');
* ctx.filter = 'grayscale(100%)';
*
* video.addEventListener('play', () => {
* var loop = () => {
* if (!video.paused && !video.ended) {
* ctx.drawImage(video, 0, 0, 300, 170);
* setTimeout(loop, 1000/ FRAME_RATE); // Drawing at 10 fps
* }
* };
* loop();
* });
* video.play();
*
* var grayVideoTrack = canvas.captureStream(FRAME_RATE).getVideoTracks()[0];
* var publisher = this.OV.initPublisher(
* myHtmlTarget,
* {
* audioSource: false,
* videoSource: grayVideoTrack
* });
* });
* ```
*/
getUserMedia(options: PublisherProperties): Promise<MediaStream> {
return new Promise<MediaStream>((resolve, reject) => {
this.generateMediaConstraints(options)
.then(constraints => {
navigator.mediaDevices.getUserMedia(constraints)
.then(mediaStream => {
resolve(mediaStream);
})
.catch(error => {
let errorName: OpenViduErrorName;
const errorMessage = error.toString();
if (!(options.videoSource === 'screen')) {
errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
} else {
errorName = OpenViduErrorName.SCREEN_CAPTURE_DENIED;
}
reject(new OpenViduError(errorName, errorMessage));
});
})
.catch((error: OpenViduError) => {
reject(error);
});
});
}
/* tslint:disable:no-empty */
/**
* Disable all logging except error level
*/
enableProdMode(): void {
console.log = () => { };
console.debug = () => { };
console.info = () => { };
console.warn = () => { };
}
/* tslint:enable:no-empty */
/**
* Set OpenVidu advanced configuration options. Currently `configuration` is an object with the following optional properties (see [[OpenViduAdvancedConfiguration]] for more details):
* - `iceServers`: set custom STUN/TURN servers to be used by OpenVidu Browser
* - `screenShareChromeExtension`: url to a custom screen share extension for Chrome to be used instead of the default one, based on ours [https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension](https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension)
* - `publisherSpeakingEventsOptions`: custom configuration for the [[PublisherSpeakingEvent]] feature
*/
setAdvancedConfiguration(configuration: OpenViduAdvancedConfiguration): void {
this.advancedConfiguration = configuration;
}
/* Hidden methods */
/**
* @hidden
*/
generateMediaConstraints(publisherProperties: PublisherProperties): Promise<MediaStreamConstraints> {
return new Promise<MediaStreamConstraints>((resolve, reject) => {
let audio, video;
if (publisherProperties.audioSource === null || publisherProperties.audioSource === false) {
audio = false;
} else if (publisherProperties.audioSource === undefined) {
audio = true;
} else {
audio = publisherProperties.audioSource;
}
if (publisherProperties.videoSource === null || publisherProperties.videoSource === false) {
video = false;
} else {
video = {
height: {
ideal: 480
},
width: {
ideal: 640
}
};
}
const mediaConstraints: MediaStreamConstraints = {
audio,
video
};
if (typeof mediaConstraints.audio === 'string') {
mediaConstraints.audio = { deviceId: { exact: mediaConstraints.audio } };
}
if (mediaConstraints.video) {
if (!!publisherProperties.resolution) {
const widthAndHeight = publisherProperties.resolution.toLowerCase().split('x');
const width = Number(widthAndHeight[0]);
const height = Number(widthAndHeight[1]);
(mediaConstraints.video as any).width.ideal = width;
(mediaConstraints.video as any).height.ideal = height;
}
if (!!publisherProperties.frameRate) {
(mediaConstraints.video as any).frameRate = { ideal: publisherProperties.frameRate };
}
if (!!publisherProperties.videoSource && typeof publisherProperties.videoSource === 'string') {
if (publisherProperties.videoSource === 'screen' ||
(platform.name!.indexOf('Firefox') !== -1 && publisherProperties.videoSource === 'window') ||
(platform.name === 'Electron' && publisherProperties.videoSource.startsWith('screen:'))) {
if (!this.checkScreenSharingCapabilities()) {
const error = new OpenViduError(OpenViduErrorName.SCREEN_SHARING_NOT_SUPPORTED, 'You can only screen share in desktop Chrome, Firefox, Opera or Electron. Detected client: ' + platform.name);
console.error(error);
reject(error);
} else {
if (platform.name === 'Electron') {
const prefix = "screen:";
const videoSourceString: string = publisherProperties.videoSource;
const electronScreenId = videoSourceString.substr(videoSourceString.indexOf(prefix) + prefix.length);
(<any>mediaConstraints['video']) = {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: electronScreenId
}
};
resolve(mediaConstraints);
} else {
if (!!this.advancedConfiguration.screenShareChromeExtension && !(platform.name!.indexOf('Firefox') !== -1) && !navigator.mediaDevices['getDisplayMedia']) {
// Custom screen sharing extension for Chrome (and Opera) and no support for MediaDevices.getDisplayMedia()
screenSharing.getScreenConstraints((error, screenConstraints) => {
if (!!error || !!screenConstraints.mandatory && screenConstraints.mandatory.chromeMediaSource === 'screen') {
if (error === 'permission-denied' || error === 'PermissionDeniedError') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop');
console.error(error);
reject(error);
} else {
const extensionId = this.advancedConfiguration.screenShareChromeExtension!.split('/').pop()!!.trim();
screenSharing.getChromeExtensionStatus(extensionId, (status) => {
if (status === 'installed-disabled') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension');
console.error(error);
reject(error);
}
if (status === 'not-installed') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, (<string>this.advancedConfiguration.screenShareChromeExtension));
console.error(error);
reject(error);
}
});
}
} else {
mediaConstraints.video = screenConstraints;
resolve(mediaConstraints);
}
});
} else {
if (navigator.mediaDevices['getDisplayMedia']) {
// getDisplayMedia support (Chrome >= 72, Firefox >= 52)
resolve(mediaConstraints);
} else {
// Default screen sharing extension for Chrome/Opera, or is Firefox < 66
const firefoxString = platform.name!.indexOf('Firefox') !== -1 ? publisherProperties.videoSource : undefined;
screenSharingAuto.getScreenId(firefoxString, (error, sourceId, screenConstraints) => {
if (!!error) {
if (error === 'not-installed') {
const extensionUrl = !!this.advancedConfiguration.screenShareChromeExtension ? this.advancedConfiguration.screenShareChromeExtension :
'https://chrome.google.com/webstore/detail/openvidu-screensharing/lfcgfepafnobdloecchnfaclibenjold';
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_NOT_INSTALLED, extensionUrl);
console.error(error);
reject(error);
} else if (error === 'installed-disabled') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_EXTENSION_DISABLED, 'You must enable the screen extension');
console.error(error);
reject(error);
} else if (error === 'permission-denied') {
const error = new OpenViduError(OpenViduErrorName.SCREEN_CAPTURE_DENIED, 'You must allow access to one window of your desktop');
console.error(error);
reject(error);
}
} else {
mediaConstraints.video = screenConstraints.video;
resolve(mediaConstraints);
}
});
}
}
publisherProperties.videoSource = 'screen';
}
}
} else {
// tslint:disable-next-line:no-string-literal
mediaConstraints.video['deviceId'] = { exact: publisherProperties.videoSource };
resolve(mediaConstraints);
}
} else {
resolve(mediaConstraints);
}
} else {
resolve(mediaConstraints);
}
});
}
/**
* @hidden
*/
startWs(onConnectSucces: (error: Error) => void): void {
const config = {
heartbeat: 5000,
sendCloseMessage: false,
ws: {
uri: this.wsUri,
useSockJS: false,
onconnected: onConnectSucces,
ondisconnect: this.disconnectCallback.bind(this),
onreconnecting: this.reconnectingCallback.bind(this),
onreconnected: this.reconnectedCallback.bind(this)
},
rpc: {
requestTimeout: 10000,
participantJoined: this.session.onParticipantJoined.bind(this.session),
participantPublished: this.session.onParticipantPublished.bind(this.session),
participantUnpublished: this.session.onParticipantUnpublished.bind(this.session),
participantLeft: this.session.onParticipantLeft.bind(this.session),
participantEvicted: this.session.onParticipantEvicted.bind(this.session),
recordingStarted: this.session.onRecordingStarted.bind(this.session),
recordingStopped: this.session.onRecordingStopped.bind(this.session),
sendMessage: this.session.onNewMessage.bind(this.session),
streamPropertyChanged: this.session.onStreamPropertyChanged.bind(this.session),
filterEventDispatched: this.session.onFilterEventDispatched.bind(this.session),
iceCandidate: this.session.recvIceCandidate.bind(this.session),
mediaError: this.session.onMediaError.bind(this.session)
}
};
this.jsonRpcClient = new RpcBuilder.clients.JsonRpcClient(config);
}
/**
* @hidden
*/
closeWs(): void {
this.jsonRpcClient.close(4102, "Connection closed by client");
}
/**
* @hidden
*/
sendRequest(method: string, params: any, callback?): void {
if (params && params instanceof Function) {
callback = params;
params = {};
}
console.debug('Sending request: {method:"' + method + '", params: ' + JSON.stringify(params) + '}');
this.jsonRpcClient.send(method, params, callback);
}
/**
* @hidden
*/
getWsUri(): string {
return this.wsUri;
}
/**
* @hidden
*/
getSecret(): string {
return this.secret;
}
/**
* @hidden
*/
getRecorder(): boolean {
return this.recorder;
}
/* Private methods */
private disconnectCallback(): void {
console.warn('Websocket connection lost');
if (this.isRoomAvailable()) {
this.session.onLostConnection('networkDisconnect');
} else {
alert('Connection error. Please reload page.');
}
}
private reconnectingCallback(): void {
console.warn('Websocket connection lost (reconnecting)');
if (!this.isRoomAvailable()) {
alert('Connection error. Please reload page.');
}
}
private reconnectedCallback(): void {
console.warn('Websocket reconnected');
if (this.isRoomAvailable()) {
this.sendRequest('connect', { sessionId: this.session.connection.rpcSessionId }, (error, response) => {
if (!!error) {
console.error(error);
this.session.onLostConnection("networkDisconnect");
this.jsonRpcClient.close(4101, "Reconnection fault");
} else {
this.jsonRpcClient.resetPing();
this.session.onRecoveredConnection();
}
});
} else {
alert('Connection error. Please reload page.');
}
}
private isRoomAvailable(): boolean {
if (this.session !== undefined && this.session instanceof Session) {
return true;
} else {
console.warn('Session instance not found');
return false;
}
}
}

View File

@ -0,0 +1,654 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { OpenVidu } from './OpenVidu';
import { Session } from './Session';
import { Stream } from './Stream';
import { StreamManager } from './StreamManager';
import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher';
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
import { Event } from '../OpenViduInternal/Events/Event';
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import platform = require('platform');
/**
* Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method
*/
export class Publisher extends StreamManager {
/**
* Whether the Publisher has been granted access to the requested input devices or not
*/
accessAllowed = false;
/**
* Whether you have called [[Publisher.subscribeToRemote]] with value `true` or `false` (*false* by default)
*/
isSubscribedToRemote = false;
/**
* The [[Session]] to which the Publisher belongs
*/
session: Session; // Initialized by Session.publish(Publisher)
private accessDenied = false;
private properties: PublisherProperties;
private permissionDialogTimeout: NodeJS.Timer;
/**
* @hidden
*/
openvidu: OpenVidu;
/**
* @hidden
*/
videoReference: HTMLVideoElement;
/**
* @hidden
*/
screenShareResizeInterval: NodeJS.Timer;
/**
* @hidden
*/
IEAdapter: any;
/**
* @hidden
*/
constructor(targEl: string | HTMLElement, properties: PublisherProperties, openvidu: OpenVidu) {
super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
this.properties = properties;
this.openvidu = openvidu;
this.stream.ee.on('local-stream-destroyed', (reason: string) => {
this.stream.isLocalStreamPublished = false;
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
this.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
});
}
/**
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same value will have no effect
*
* #### Events dispatched
*
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
*
* The [[Session]] object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
* The [[Publisher]] object of the local participant will also dispatch the exact same event
*
* The [[Session]] object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
* The respective [[Subscriber]] object of every other participant receiving this Publisher's stream will also dispatch the exact same event
*
* See [[StreamPropertyChangedEvent]] to learn more.
*
* @param value `true` to publish the audio stream, `false` to unpublish it
*/
publishAudio(value: boolean): void {
if (this.stream.audioActive !== value) {
this.stream.getMediaStream().getAudioTracks().forEach((track) => {
track.enabled = value;
});
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'audioActive',
newValue: value,
reason: 'publishAudio'
},
(error, response) => {
if (error) {
console.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', value, !value, 'publishAudio')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'audioActive', value, !value, 'publishAudio')]);
}
});
}
this.stream.audioActive = value;
console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its audio stream');
}
}
/**
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same value will have no effect
*
* #### Events dispatched
*
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
*
* The [[Session]] object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
* The [[Publisher]] object of the local participant will also dispatch the exact same event
*
* The [[Session]] object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
* The respective [[Subscriber]] object of every other participant receiving this Publisher's stream will also dispatch the exact same event
*
* See [[StreamPropertyChangedEvent]] to learn more.
*
* @param value `true` to publish the video stream, `false` to unpublish it
*/
publishVideo(value: boolean): void {
if (this.stream.videoActive !== value) {
this.stream.getMediaStream().getVideoTracks().forEach((track) => {
track.enabled = value;
});
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'videoActive',
newValue: value,
reason: 'publishVideo'
},
(error, response) => {
if (error) {
console.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoActive', value, !value, 'publishVideo')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoActive', value, !value, 'publishVideo')]);
}
});
}
this.stream.videoActive = value;
console.info("'Publisher' has " + (value ? 'published' : 'unpublished') + ' its video stream');
}
}
/**
* Call this method before [[Session.publish]] if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
*/
subscribeToRemote(value?: boolean): void {
value = (value !== undefined) ? value : true;
this.isSubscribedToRemote = value;
this.stream.subscribeToMyRemote(value);
}
/**
* See [[EventDispatcher.on]]
*/
on(type: string, handler: (event: Event) => void): EventDispatcher {
super.on(type, handler);
if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
} else {
this.stream.ee.on('stream-created-by-publisher', () => {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
});
}
}
if (type === 'remoteVideoPlaying') {
if (this.stream.displayMyRemote() && this.videos[0] && this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) {
this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
}
}
if (type === 'accessAllowed') {
if (this.accessAllowed) {
this.emitEvent('accessAllowed', []);
}
}
if (type === 'accessDenied') {
if (this.accessDenied) {
this.emitEvent('accessDenied', []);
}
}
return this;
}
/**
* See [[EventDispatcher.once]]
*/
once(type: string, handler: (event: Event) => void): Publisher {
super.once(type, handler);
if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
} else {
this.stream.ee.once('stream-created-by-publisher', () => {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
});
}
}
if (type === 'remoteVideoPlaying') {
if (this.stream.displayMyRemote() && this.videos[0] && this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) {
this.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
}
}
if (type === 'accessAllowed') {
if (this.accessAllowed) {
this.emitEvent('accessAllowed', []);
}
}
if (type === 'accessDenied') {
if (this.accessDenied) {
this.emitEvent('accessDenied', []);
}
}
return this;
}
/* Hidden methods */
/**
* @hidden
*/
initialize(): Promise<any> {
return new Promise((resolve, reject) => {
let constraints: MediaStreamConstraints = {};
let constraintsAux: MediaStreamConstraints = {};
const timeForDialogEvent = 1250;
let startTime;
const errorCallback = (openViduError: OpenViduError) => {
this.accessDenied = true;
this.accessAllowed = false;
reject(openViduError);
};
const successCallback = (mediaStream: MediaStream) => {
this.accessAllowed = true;
this.accessDenied = false;
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource));
}
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
}
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
if (!!mediaStream.getAudioTracks()[0]) {
const enabled = (this.stream.audioActive !== undefined && this.stream.audioActive !== null) ? this.stream.audioActive : !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
mediaStream.getAudioTracks()[0].enabled = enabled;
}
if (!!mediaStream.getVideoTracks()[0]) {
const enabled = (this.stream.videoActive !== undefined && this.stream.videoActive !== null) ? this.stream.videoActive : !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
mediaStream.getVideoTracks()[0].enabled = enabled;
}
this.videoReference = document.createElement('video');
if (platform.name === 'Safari') {
this.videoReference.setAttribute('playsinline', 'true');
}
this.stream.setMediaStream(mediaStream);
if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
}
this.videoReference.srcObject = mediaStream;
if (!this.stream.displayMyRemote()) {
// When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
// avoid early 'streamPlaying' event
this.stream.updateMediaStreamInVideos();
}
delete this.firstVideoElement;
if (this.stream.isSendVideo()) {
if (!this.stream.isSendScreen()) {
if (platform['isIonicIos'] || platform.name === 'Safari') {
// iOS Ionic or Safari. Limitation: cannot set videoDimensions directly, as the videoReference is not loaded
// if not added to DOM. Must add it to DOM and wait for videoWidth and videoHeight properties to be defined
this.videoReference.style.display = 'none';
document.body.appendChild(this.videoReference);
const videoDimensionsSet = () => {
this.stream.videoDimensions = {
width: this.videoReference.videoWidth,
height: this.videoReference.videoHeight
};
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
document.body.removeChild(this.videoReference);
};
let interval;
this.videoReference.addEventListener('loadedmetadata', () => {
if (this.videoReference.videoWidth === 0) {
interval = setInterval(() => {
if (this.videoReference.videoWidth !== 0) {
clearInterval(interval);
videoDimensionsSet();
}
}, 40);
} else {
videoDimensionsSet();
}
});
} else {
// Rest of platforms
// With no screen share, video dimension can be set directly from MediaStream (getSettings)
// Orientation must be checked for mobile devices (width and height are reversed)
const { width, height } = mediaStream.getVideoTracks()[0].getSettings();
if ((platform.os!!.family === 'iOS' || platform.os!!.family === 'Android') && (window.innerHeight > window.innerWidth)) {
// Mobile portrait mode
this.stream.videoDimensions = {
width: height || 0,
height: width || 0
};
} else {
this.stream.videoDimensions = {
width: width || 0,
height: height || 0
};
}
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
}
} else {
// With screen share, video dimension must be got from a video element (onloadedmetadata event)
this.videoReference.addEventListener('loadedmetadata', () => {
this.stream.videoDimensions = {
width: this.videoReference.videoWidth,
height: this.videoReference.videoHeight
};
this.screenShareResizeInterval = setInterval(() => {
const firefoxSettings = mediaStream.getVideoTracks()[0].getSettings();
const newWidth = (platform.name === 'Chrome' || platform.name === 'Opera') ? this.videoReference.videoWidth : firefoxSettings.width;
const newHeight = (platform.name === 'Chrome' || platform.name === 'Opera') ? this.videoReference.videoHeight : firefoxSettings.height;
if (this.stream.isLocalStreamPublished &&
(newWidth !== this.stream.videoDimensions.width ||
newHeight !== this.stream.videoDimensions.height)) {
const oldValue = { width: this.stream.videoDimensions.width, height: this.stream.videoDimensions.height };
this.stream.videoDimensions = {
width: newWidth || 0,
height: newHeight || 0
};
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'videoDimensions',
newValue: JSON.stringify(this.stream.videoDimensions),
reason: 'screenResized'
},
(error, response) => {
if (error) {
console.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this.session, this.stream, 'videoDimensions', this.stream.videoDimensions, oldValue, 'screenResized')]);
this.emitEvent('streamPropertyChanged', [new StreamPropertyChangedEvent(this, this.stream, 'videoDimensions', this.stream.videoDimensions, oldValue, 'screenResized')]);
}
});
}
}, 500);
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
});
}
} else {
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
}
resolve();
};
const getMediaSuccess = (mediaStream: MediaStream, definedAudioConstraint) => {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (this.stream.isSendScreen() && this.stream.isSendAudio()) {
// When getting desktop as user media audio constraint must be false. Now we can ask for it if required
constraintsAux.audio = definedAudioConstraint;
constraintsAux.video = false;
startTime = Date.now();
this.setPermissionDialogTimer(timeForDialogEvent);
navigator.mediaDevices.getUserMedia(constraintsAux)
.then(audioOnlyStream => {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
successCallback(mediaStream);
})
.catch(error => {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (error.name === 'Error') {
// Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
error.name = error.constructor.name;
}
let errorName, errorMessage;
switch (error.name.toLowerCase()) {
case 'notfounderror':
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
case 'notallowederror':
errorName = OpenViduErrorName.DEVICE_ACCESS_DENIED;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
case 'overconstrainederror':
if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
} else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
}
errorCallback(new OpenViduError(errorName, errorMessage));
break;
}
});
} else {
successCallback(mediaStream);
}
};
const getMediaError = error => {
console.error(error);
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (error.name === 'Error') {
// Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
error.name = error.constructor.name;
}
let errorName, errorMessage;
switch (error.name.toLowerCase()) {
case 'notfounderror':
navigator.mediaDevices.getUserMedia({
audio: false,
video: constraints.video
})
.then(mediaStream => {
mediaStream.getVideoTracks().forEach((track) => {
track.stop();
});
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
}).catch(e => {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
});
break;
case 'notallowederror':
errorName = this.stream.isSendScreen() ? OpenViduErrorName.SCREEN_CAPTURE_DENIED : OpenViduErrorName.DEVICE_ACCESS_DENIED;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
case 'overconstrainederror':
navigator.mediaDevices.getUserMedia({
audio: false,
video: constraints.video
})
.then(mediaStream => {
mediaStream.getVideoTracks().forEach((track) => {
track.stop();
});
if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = "Audio input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact + "' not found";
} else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
}
errorCallback(new OpenViduError(errorName, errorMessage));
}).catch(e => {
if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = "Video input device with deviceId '" + (<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact + "' not found";
} else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage = "Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
}
errorCallback(new OpenViduError(errorName, errorMessage));
});
break;
case 'aborterror':
case 'notreadableerror':
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
default:
errorName = OpenViduErrorName.GENERIC_ERROR;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
}
}
// Check if new constraints need to be generated. No constraints needed if
// - video track is given and no audio
// - audio track is given and no video
// - both video and audio tracks are given
if ((typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack && !this.properties.audioSource)
|| (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack && !this.properties.videoSource)
|| (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack && this.properties.audioSource instanceof MediaStreamTrack)) {
const mediaStream = new MediaStream();
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.addTrack((<MediaStreamTrack>this.properties.videoSource));
}
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.addTrack((<MediaStreamTrack>this.properties.audioSource));
}
// MediaStreamTracks are handled within callback - just call callback with new MediaStream() and let it handle the sources
successCallback(mediaStream);
// Return as we do not need to process further
return;
}
this.openvidu.generateMediaConstraints(this.properties)
.then(myConstraints => {
constraints = myConstraints;
const outboundStreamOptions = {
mediaConstraints: constraints,
publisherProperties: this.properties
};
this.stream.setOutboundStreamOptions(outboundStreamOptions);
if (this.stream.isSendVideo() || this.stream.isSendAudio()) {
const definedAudioConstraint = ((constraints.audio === undefined) ? true : constraints.audio);
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
constraintsAux.video = constraints.video;
startTime = Date.now();
this.setPermissionDialogTimer(timeForDialogEvent);
if (this.stream.isSendScreen() && navigator.mediaDevices['getDisplayMedia'] && platform.name !== 'Electron') {
navigator.mediaDevices['getDisplayMedia']({ video: true })
.then(mediaStream => {
getMediaSuccess(mediaStream, definedAudioConstraint);
})
.catch(error => {
getMediaError(error);
});
} else {
navigator.mediaDevices.getUserMedia(constraintsAux)
.then(mediaStream => {
getMediaSuccess(mediaStream, definedAudioConstraint);
})
.catch(error => {
getMediaError(error);
});
}
} else {
reject(new OpenViduError(OpenViduErrorName.NO_INPUT_SOURCE_SET,
"Properties 'audioSource' and 'videoSource' cannot be set to false or null at the same time when calling 'OpenVidu.initPublisher'"));
}
})
.catch((error: OpenViduError) => {
errorCallback(error);
});
});
}
/**
* @hidden
*/
reestablishStreamPlayingEvent() {
if (this.ee.getListeners('streamPlaying').length > 0) {
this.addPlayEventToFirstVideo();
}
}
/* Private methods */
private setPermissionDialogTimer(waitTime: number): void {
this.permissionDialogTimeout = setTimeout(() => {
this.emitEvent('accessDialogOpened', []);
}, waitTime);
}
private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
clearTimeout(this.permissionDialogTimeout);
if ((Date.now() - startTime) > waitTime) {
// Permission dialog was shown and now is closed
this.emitEvent('accessDialogClosed', []);
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,478 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Stream } from './Stream';
import { EventDispatcher } from '../OpenViduInternal/Interfaces/Public/EventDispatcher';
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
import { Event } from '../OpenViduInternal/Events/Event';
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import EventEmitter = require('wolfy87-eventemitter');
import platform = require('platform');
/**
* Interface in charge of displaying the media streams in the HTML DOM. This wraps any [[Publisher]] and [[Subscriber]] object.
* You can insert as many video players fo the same Stream as you want by calling [[StreamManager.addVideoElement]] or
* [[StreamManager.createVideoElement]].
*
* The use of StreamManager wrapper is particularly useful when you don't need to differentiate between Publisher or Subscriber streams or just
* want to directly manage your own video elements (even more than one video element per Stream). This scenario is pretty common in
* declarative, MVC frontend frameworks such as Angular, React or Vue.js
*/
export class StreamManager implements EventDispatcher {
/**
* The Stream represented in the DOM by the Publisher/Subscriber
*/
stream: Stream;
/**
* All the videos displaying the Stream of this Publisher/Subscriber
*/
videos: StreamManagerVideo[] = [];
/**
* Whether the Stream represented in the DOM is local or remote
* - `false` for [[Publisher]]
* - `true` for [[Subscriber]]
*/
remote: boolean;
/**
* The DOM HTMLElement assigned as target element when creating the video for the Publisher/Subscriber. This property is only defined if:
* - [[Publisher]] has been initialized by calling method [[OpenVidu.initPublisher]] with a valid `targetElement` parameter
* - [[Subscriber]] has been initialized by calling method [[Session.subscribe]] with a valid `targetElement` parameter
*/
targetElement: HTMLElement;
/**
* `id` attribute of the DOM video element displaying the Publisher/Subscriber's stream. This property is only defined if:
* - [[Publisher]] has been initialized by calling method [[OpenVidu.initPublisher]] with a valid `targetElement` parameter
* - [[Subscriber]] has been initialized by calling method [[Session.subscribe]] with a valid `targetElement` parameter
*/
id: string;
/**
* @hidden
*/
firstVideoElement: StreamManagerVideo;
/**
* @hidden
*/
lazyLaunchVideoElementCreatedEvent = false;
/**
* @hidden
*/
element: HTMLElement;
/**
* @hidden
*/
ee = new EventEmitter();
/**
* @hidden
*/
protected canPlayListener: EventListener;
/**
* @hidden
*/
constructor(stream: Stream, targetElement?: HTMLElement | string) {
this.stream = stream;
this.stream.streamManager = this;
this.remote = !this.stream.isLocal();
if (!!targetElement) {
let targEl;
if (typeof targetElement === 'string') {
targEl = document.getElementById(targetElement);
} else if (targetElement instanceof HTMLElement) {
targEl = targetElement;
}
if (!!targEl) {
this.firstVideoElement = {
targetElement: targEl,
video: document.createElement('video'),
id: '',
canplayListenerAdded: false
};
if (platform.name === 'Safari') {
this.firstVideoElement.video.setAttribute('playsinline', 'true');
}
this.targetElement = targEl;
this.element = targEl;
}
}
this.canPlayListener = () => {
if (this.stream.isLocal()) {
if (!this.stream.displayMyRemote()) {
console.info("Your local 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
} else {
console.info("Your own remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('remoteVideoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'remoteVideoPlaying')]);
}
} else {
console.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
}
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
};
}
/**
* See [[EventDispatcher.on]]
*/
on(type: string, handler: (event: Event) => void): EventDispatcher {
this.ee.on(type, event => {
if (event) {
console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", event);
} else {
console.info("Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'");
}
handler(event);
});
if (type === 'videoElementCreated') {
if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
this.lazyLaunchVideoElementCreatedEvent = false;
}
}
if (type === 'streamPlaying' || type === 'videoPlaying') {
if (this.videos[0] && this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
}
}
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) {
this.stream.enableVolumeChangeEvent();
}
return this;
}
/**
* See [[EventDispatcher.once]]
*/
once(type: string, handler: (event: Event) => void): StreamManager {
this.ee.once(type, event => {
if (event) {
console.info("Event '" + type + "' triggered once", event);
} else {
console.info("Event '" + type + "' triggered once");
}
handler(event);
});
if (type === 'videoElementCreated') {
if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
}
}
if (type === 'streamPlaying' || type === 'videoPlaying') {
if (this.videos[0] && this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
}
}
if (type === 'streamAudioVolumeChange' && this.stream.hasAudio) {
this.stream.enableOnceVolumeChangeEvent();
}
return this;
}
/**
* See [[EventDispatcher.off]]
*/
off(type: string, handler?: (event: Event) => void): StreamManager {
if (!handler) {
this.ee.removeAllListeners(type);
} else {
this.ee.off(type, handler);
}
if (type === 'streamAudioVolumeChange') {
this.stream.disableVolumeChangeEvent();
}
return this;
}
/**
* Makes `video` element parameter display this [[stream]]. This is useful when you are
* [managing the video elements on your own](/docs/how-do-i/manage-videos/#you-take-care-of-the-video-players)
*
* Calling this method with a video already added to other Publisher/Subscriber will cause the video element to be
* disassociated from that previous Publisher/Subscriber and to be associated to this one.
*
* @returns 1 if the video wasn't associated to any other Publisher/Subscriber and has been successfully added to this one.
* 0 if the video was already added to this Publisher/Subscriber. -1 if the video was previously associated to any other
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
*/
addVideoElement(video: HTMLVideoElement): number {
this.initializeVideoProperties(video);
if (this.stream.isLocal() && this.stream.displayMyRemote()) {
if (video.srcObject !== this.stream.getMediaStream()) {
video.srcObject = this.stream.getMediaStream();
}
}
// If the video element is already part of this StreamManager do nothing
for (const v of this.videos) {
if (v.video === video) {
return 0;
}
}
let returnNumber = 1;
for (const streamManager of this.stream.session.streamManagers) {
if (streamManager.disassociateVideo(video)) {
returnNumber = -1;
break;
}
}
this.stream.session.streamManagers.forEach(streamManager => {
streamManager.disassociateVideo(video);
});
this.pushNewStreamManagerVideo({
video,
id: video.id,
canplayListenerAdded: false
});
console.info('New video element associated to ', this);
return returnNumber;
}
/**
* Creates a new video element displaying this [[stream]]. This allows you to have multiple video elements displaying the same media stream.
*
* #### Events dispatched
*
* The Publisher/Subscriber object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM. See [[VideoElementEvent]]
*
* @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted
* @param insertMode How the video element will be inserted accordingly to `targetElemet`
*
* @returns The created HTMLVideoElement
*/
createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement {
let targEl;
if (typeof targetElement === 'string') {
targEl = document.getElementById(targetElement);
if (!targEl) {
throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement);
}
} else if (targetElement instanceof HTMLElement) {
targEl = targetElement;
} else {
throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement);
}
const video = document.createElement('video');
this.initializeVideoProperties(video);
let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND;
switch (insMode) {
case VideoInsertMode.AFTER:
targEl.parentNode!!.insertBefore(video, targEl.nextSibling);
break;
case VideoInsertMode.APPEND:
targEl.appendChild(video);
break;
case VideoInsertMode.BEFORE:
targEl.parentNode!!.insertBefore(video, targEl);
break;
case VideoInsertMode.PREPEND:
targEl.insertBefore(video, targEl.childNodes[0]);
break;
case VideoInsertMode.REPLACE:
targEl.parentNode!!.replaceChild(video, targEl);
break;
default:
insMode = VideoInsertMode.APPEND;
targEl.appendChild(video);
break;
}
const v: StreamManagerVideo = {
targetElement: targEl,
video,
insertMode: insMode,
id: video.id,
canplayListenerAdded: false
};
this.pushNewStreamManagerVideo(v);
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]);
this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement;
return video;
}
/**
* @hidden
*/
initializeVideoProperties(video: HTMLVideoElement): void {
if (!(this.stream.isLocal() && this.stream.displayMyRemote())) {
// Avoid setting the MediaStream into the srcObject if remote subscription before publishing
if (video.srcObject !== this.stream.getMediaStream()) {
// If srcObject already set don't do it again
video.srcObject = this.stream.getMediaStream();
}
}
video.autoplay = true;
video.controls = false;
if (platform.name === 'Safari') {
video.setAttribute('playsinline', 'true');
}
if (!video.id) {
video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
// DEPRECATED property: assign once the property id if the user provided a valid targetElement
if (!this.id && !!this.targetElement) {
this.id = video.id;
}
}
if (!this.remote && !this.stream.displayMyRemote()) {
video.muted = true;
if (video.style.transform === 'rotateY(180deg)' && !this.stream.outboundStreamOpts.publisherProperties.mirror) {
// If the video was already rotated and now is set to not mirror
this.removeMirrorVideo(video);
} else if (this.stream.outboundStreamOpts.publisherProperties.mirror && !this.stream.isSendScreen()) {
this.mirrorVideo(video);
}
}
}
/**
* @hidden
*/
removeAllVideos(): void {
for (let i = this.stream.session.streamManagers.length - 1; i >= 0; --i) {
if (this.stream.session.streamManagers[i] === this) {
this.stream.session.streamManagers.splice(i, 1);
}
}
this.videos.forEach(streamManagerVideo => {
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
streamManagerVideo.canplayListenerAdded = false;
if (!!streamManagerVideo.targetElement) {
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
this.ee.emitEvent('videoElementDestroyed', [new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')]);
}
// Remove srcObject from the video
streamManagerVideo.video.srcObject = null;
// Remove from collection of videos every video managed by OpenVidu Browser
this.videos.filter(v => !v.targetElement);
});
}
/**
* @hidden
*/
disassociateVideo(video: HTMLVideoElement): boolean {
let disassociated = false;
for (let i = 0; i < this.videos.length; i++) {
if (this.videos[i].video === video) {
this.videos[i].video.removeEventListener('canplay', this.canPlayListener);
this.videos.splice(i, 1);
disassociated = true;
console.info('Video element disassociated from ', this);
break;
}
}
return disassociated;
}
/**
* @hidden
*/
addPlayEventToFirstVideo() {
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
this.videos[0].canplayListenerAdded = true;
}
}
/**
* @hidden
*/
updateMediaStream(mediaStream: MediaStream) {
this.videos.forEach(streamManagerVideo => {
streamManagerVideo.video.srcObject = mediaStream;
if (platform['isIonicIos']) {
// iOS Ionic. LIMITATION: must reinsert the video in the DOM for
// the media stream to be updated
const vParent = streamManagerVideo.video.parentElement;
const newVideo = streamManagerVideo.video;
vParent!!.replaceChild(newVideo, streamManagerVideo.video);
streamManagerVideo.video = newVideo;
}
});
}
/**
* @hidden
*/
emitEvent(type: string, eventArray: any[]): void {
this.ee.emitEvent(type, eventArray);
}
private pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) {
this.videos.push(streamManagerVideo);
this.addPlayEventToFirstVideo();
if (this.stream.session.streamManagers.indexOf(this) === -1) {
this.stream.session.streamManagers.push(this);
}
}
private mirrorVideo(video): void {
if (!platform['isIonicIos']) {
video.style.transform = 'rotateY(180deg)';
video.style.webkitTransform = 'rotateY(180deg)';
}
}
private removeMirrorVideo(video): void {
video.style.transform = 'unset';
video.style.webkitTransform = 'unset';
}
}

View File

@ -0,0 +1,67 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Stream } from './Stream';
import { StreamManager } from './StreamManager';
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
/**
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method
*/
export class Subscriber extends StreamManager {
/**
* @hidden
*/
properties: SubscriberProperties;
/**
* @hidden
*/
constructor(stream: Stream, targEl: string | HTMLElement, properties: SubscriberProperties) {
super(stream, targEl);
this.element = this.targetElement;
this.stream = stream;
this.properties = properties;
}
/**
* Subscribe or unsubscribe from the audio stream (if available). Calling this method twice in a row passing same value will have no effect
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
*/
subscribeToAudio(value: boolean): Subscriber {
this.stream.getMediaStream().getAudioTracks().forEach((track) => {
track.enabled = value;
});
console.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its audio stream');
return this;
}
/**
* Subscribe or unsubscribe from the video stream (if available). Calling this method twice in a row passing same value will have no effect
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
*/
subscribeToVideo(value: boolean): Subscriber {
this.stream.getMediaStream().getVideoTracks().forEach((track) => {
track.enabled = value;
});
console.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its video stream');
return this;
}
}

View File

@ -0,0 +1,35 @@
{
//"allowUnusedLabels": true,
"allowUnreachableCode": false,
"buildOnSave": false,
"compileOnSave": true,
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"emitBOM": false,
"forceConsistentCasingInFileNames": true,
"lib": [
"dom",
"es2015.promise",
"es5",
"scripthost"
],
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
//"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"outDir": "../../lib",
"preserveConstEnums": true,
"removeComments": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5"
}
}

View File

@ -0,0 +1,23 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
export enum LocalRecorderState {
READY = 'READY',
RECORDING = 'RECORDING',
PAUSED = 'PAUSED',
FINISHED = 'FINISHED'
}

View File

@ -0,0 +1,124 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
/**
* Defines property [[OpenViduError.name]]
*/
export enum OpenViduErrorName {
/**
* Browser is not supported by OpenVidu.
* Returned upon unsuccessful [[Session.connect]]
*/
BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',
/**
* The user hasn't granted permissions to the required input device when the browser asked for them.
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
*/
DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED',
/**
* The required input device is probably being used by other process when the browser asked for it.
* Accuracy of this property is only granted for Chrome and Firefox clients.
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
*/
DEVICE_ALREADY_IN_USE = "DEVICE_ALREADY_IN_USE",
/**
* The user hasn't granted permissions to capture some desktop screen when the browser asked for them.
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
*/
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
/**
* Browser does not support screen sharing.
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
/**
* Only for Chrome, there's no screen sharing extension installed
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',
/**
* Only for Chrome, the screen sharing extension is installed but is disabled
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED',
/**
* No video input device found with the provided deviceId (property [[PublisherProperties.videoSource]])
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
INPUT_VIDEO_DEVICE_NOT_FOUND = 'INPUT_VIDEO_DEVICE_NOT_FOUND',
/**
* No audio input device found with the provided deviceId (property [[PublisherProperties.audioSource]])
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
INPUT_AUDIO_DEVICE_NOT_FOUND = 'INPUT_AUDIO_DEVICE_NOT_FOUND',
/**
* Method [[OpenVidu.initPublisher]] has been called with properties `videoSource` and `audioSource` of
* [[PublisherProperties]] parameter both set to *false* or *null*
*/
NO_INPUT_SOURCE_SET = 'NO_INPUT_SOURCE_SET',
/**
* Some media property of [[PublisherProperties]] such as `frameRate` or `resolution` is not supported
* by the input devices (whenever it is possible they are automatically adjusted to the most similar value).
* Returned upon unsuccessful [[OpenVidu.initPublisher]]
*/
PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR',
/**
* The client tried to call a method without the required permissions. This can occur for methods [[Session.publish]],
* [[Session.forceUnpublish]], [[Session.forceDisconnect]], [[Stream.applyFilter]], [[Stream.removeFilter]]
*/
OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED',
/**
* _Not in use yet_
*/
OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED',
/**
* _Not in use yet_
*/
GENERIC_ERROR = 'GENERIC_ERROR'
}
/**
* Simple object to identify runtime errors on the client side
*/
export class OpenViduError {
name: OpenViduErrorName;
message: string;
/**
* @hidden
*/
constructor(name: OpenViduErrorName, message: string) {
this.name = name;
this.message = message;
}
}

View File

@ -0,0 +1,44 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
/**
* How the video will be inserted in the DOM for Publishers and Subscribers. See [[PublisherProperties.insertMode]] and [[SubscriberProperties.insertMode]]
*/
export enum VideoInsertMode {
/**
* Video inserted after the target element (as next sibling)
*/
AFTER = 'AFTER',
/**
* Video inserted as last child of the target element
*/
APPEND = 'APPEND',
/**
* Video inserted before the target element (as previous sibling)
*/
BEFORE = 'BEFORE',
/**
* Video inserted as first child of the target element
*/
PREPEND = 'PREPEND',
/**
* Video replaces target element
*/
REPLACE = 'REPLACE'
}

View File

@ -0,0 +1,62 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
/**
* Defines the following events:
* - `connectionCreated`: dispatched by [[Session]]
* - `connectionDestroyed`: dispatched by [[Session]]
*/
export class ConnectionEvent extends Event {
/**
* Connection object that was created or destroyed
*/
connection: Connection;
/**
* For `connectionDestroyed` event:
* - "disconnect": the remote user has called `Session.disconnect()`
* - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the remote user network connection has dropped
*
* For `connectionCreated` event an empty string
*/
reason: string;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session, type: string, connection: Connection, reason: string) {
super(cancelable, target, type);
this.connection = connection;
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,86 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Filter } from '../../OpenVidu/Filter';
import { StreamManager } from '../../OpenVidu/StreamManager';
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
export abstract class Event {
/**
* Whether the event has a default behavior that may be prevented by calling [[Event.preventDefault]]
*/
cancelable: boolean;
/**
* The object that dispatched the event
*/
target: Session | StreamManager | Filter;
/**
* The type of event. This is the same string you pass as first parameter when calling method `on()` of any object implementing [[EventDispatcher]] interface
*/
type: string;
/**
* @hidden
*/
hasBeenPrevented = false;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session | StreamManager | Filter, type: string) {
this.cancelable = cancelable;
this.target = target;
this.type = type;
}
/**
* Whether the default beahivour of the event has been prevented or not. Call [[Event.preventDefault]] to prevent it
*/
isDefaultPrevented(): boolean {
return this.hasBeenPrevented;
}
/**
* Prevents the default behavior of the event. The following events have a default behavior:
*
* - `sessionDisconnected`: dispatched by [[Session]] object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
* by calling [[Subscriber.createVideoElement]]). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
*
* - `streamDestroyed`:
* - If dispatched by a [[Publisher]] (*you* have unpublished): automatically stops all media tracks and deletes any HTML video element associated to it (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement`
* in method [[OpenVidu.initPublisher]] or by calling [[Publisher.createVideoElement]]). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event.
* - If dispatched by [[Session]] (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the WebRTCPeer connection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to that Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method [[Session.subscribe]] or
* by calling [[Subscriber.createVideoElement]]). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event.
*/
preventDefault() {
// tslint:disable-next-line:no-empty
this.callDefaultBehavior = () => { };
this.hasBeenPrevented = true;
}
/**
* @hidden
*/
abstract callDefaultBehavior();
}

View File

@ -0,0 +1,47 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Stream } from '../../OpenVidu/Stream';
import { Filter } from '../../OpenVidu/Filter';
/**
* Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling [[Filter.addEventListener]]
*/
export class FilterEvent extends Event {
/**
* Data of the event
*/
data: Object;
/**
* @hidden
*/
constructor(target: Filter, eventType: string, data: Object) {
super(false, target, eventType);
this.data = data;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,62 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../..';
/**
* Defines the following events:
* - `publisherStartSpeaking`: dispatched by [[Session]]
* - `publisherStopSpeaking`: dispatched by [[Session]]
*
* More information:
* - This events will only be triggered for **remote streams that have audio tracks** ([[Stream.hasAudio]] must be true)
* - Both events share the same lifecycle. That means that you can subscribe to only one of them if you want, but if you call `Session.off('publisherStopSpeaking')`,
* keep in mind that this will also internally remove any 'publisherStartSpeaking' event
* - You can further configure how the events are dispatched by setting property `publisherSpeakingEventsOptions` in the call of [[OpenVidu.setAdvancedConfiguration]]
*/
export class PublisherSpeakingEvent extends Event {
/**
* The client that started or stopped speaking
*/
connection: Connection;
/**
* The streamId of the Stream affected by the speaking event
*/
streamId: string;
/**
* @hidden
*/
constructor(target: Session, type: string, connection: Connection, streamId: string) {
super(false, target, type);
this.type = type;
this.connection = connection;
this.streamId = streamId;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,73 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
/**
* Defines the following events:
* - `recordingStarted`: dispatched by [[Session]]
* - `recordingStopped`: dispatched by [[Session]]
*/
export class RecordingEvent extends Event {
/**
* The recording ID generated in openvidu-server
*/
id: string;
/**
* The recording name you supplied to openvidu-server. For example, to name your recording file MY_RECORDING:
* - With **API REST**: POST to `/api/recordings/start` passing JSON body `{"session":"sessionId","name":"MY_RECORDING"}`
* - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name("MY_RECORDING").build())`
* - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, {name: "MY_RECORDING"})`
*
* If no name is supplied, this property will be undefined and the recorded file will be named after property [[id]]
*/
name?: string;
/**
* For 'recordingStopped' event:
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "automaticStop": see [Automatic stop of recordings](https://openvidu.io/docs/advanced-features/recording/#automatic-stop-of-recordings)
* - "mediaServerDisconnect": OpenVidu Media Server has crashed or lost its connection. A new media server instance is active and the recording has been stopped (no media streams are available in the new media server)
*
* For 'recordingStarted' empty string
*/
reason?: string;
/**
* @hidden
*/
constructor(target: Session, type: string, id: string, name: string, reason?: string) {
super(false, target, type);
this.id = id;
if (name !== id) {
this.name = name;
}
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,68 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
/**
* Defines event `sessionDisconnected` dispatched by [[Session]]
*/
export class SessionDisconnectedEvent extends Event {
/**
* - "disconnect": you have called `Session.disconnect()`
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": you have been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": your network connection has dropped
*/
reason: string;
/**
* @hidden
*/
constructor(target: Session, reason: string) {
super(true, target, 'sessionDisconnected');
this.reason = reason;
}
/**
* @hidden
*/
callDefaultBehavior() {
console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
const session = <Session>this.target;
// Dispose and delete all remote Connections
for (const connectionId in session.remoteConnections) {
if (!!session.remoteConnections[connectionId].stream) {
session.remoteConnections[connectionId].stream.disposeWebRtcPeer();
session.remoteConnections[connectionId].stream.disposeMediaStream();
if (session.remoteConnections[connectionId].stream.streamManager) {
session.remoteConnections[connectionId].stream.streamManager.removeAllVideos();
}
delete session.remoteStreamsCreated[session.remoteConnections[connectionId].stream.streamId];
session.remoteConnections[connectionId].dispose();
}
delete session.remoteConnections[connectionId];
}
}
}

View File

@ -0,0 +1,65 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
/**
* Defines the following events:
* - `signal`: dispatched by [[Session]]
* - `signal:TYPE`: dispatched by [[Session]]
*/
export class SignalEvent extends Event {
/**
* The type of signal (can be empty).
*
* The client must be subscribed to `Session.on('signal:type', function(signalEvent) {...})` to receive this object in the callback.
*
* Subscribing to `Session.on('signal', function(signalEvent) {...})` will trigger all types of signals.
*/
type: string;
/**
* The message of the signal (can be emtpy)
*/
data: string;
/**
* The client that sent the signal
*/
from: Connection;
/**
* @hidden
*/
constructor(target: Session, type: string, data: string, from: Connection) {
super(false, target, type);
this.type = type;
this.data = data;
this.from = from;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,111 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Publisher } from '../../OpenVidu/Publisher';
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
/**
* Defines the following events:
* - `streamCreated`: dispatched by [[Session]] and [[Publisher]]
* - `streamDestroyed`: dispatched by [[Session]] and [[Publisher]]
*/
export class StreamEvent extends Event {
/**
* Stream object that was created or destroyed
*/
stream: Stream;
/**
* For 'streamDestroyed' event:
* - "unpublish": method `Session.unpublish()` has been called
* - "disconnect": method `Session.disconnect()` has been called
* - "forceUnpublishByUser": some user has called `Session.forceUnpublish()` over the Stream
* - "forceDisconnectByUser": some user has called `Session.forceDisconnect()` over the Stream
* - "forceUnpublishByServer": the user's stream has been unpublished from the Session by the application
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the user's network connection has dropped
* - "mediaServerDisconnect": OpenVidu Media Server has crashed or lost its connection. A new media server instance is active and no media streams are available in the media server
*
* For 'streamCreated' empty string
*/
reason: string;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session | Publisher, type: string, stream: Stream, reason: string) {
super(cancelable, target, type);
this.stream = stream;
this.reason = reason;
}
/**
* @hidden
*/
callDefaultBehavior() {
if (this.type === 'streamDestroyed') {
if (this.target instanceof Session) {
// Remote Stream
console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
this.stream.disposeWebRtcPeer();
} else if (this.target instanceof Publisher) {
// Local Stream
console.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Publisher'");
clearInterval((<Publisher>this.target).screenShareResizeInterval);
this.stream.isLocalStreamReadyToPublish = false;
// Delete Publisher object from OpenVidu publishers array
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
for (let i = 0; i < openviduPublishers.length; i++) {
if (openviduPublishers[i] === (<Publisher>this.target)) {
openviduPublishers.splice(i, 1);
break;
}
}
}
// Dispose the MediaStream local object
this.stream.disposeMediaStream();
// Remove from DOM all video elements associated to this Stream, if there's a StreamManager defined
// (method Session.subscribe must have been called)
if (this.stream.streamManager) this.stream.streamManager.removeAllVideos();
// Delete stream from Session.remoteStreamsCreated map
delete this.stream.session.remoteStreamsCreated[this.stream.streamId];
// Delete StreamOptionsServer from remote Connection
const remoteConnection = this.stream.session.remoteConnections[this.stream.connection.connectionId];
if (!!remoteConnection && !!remoteConnection.options) {
const streamOptionsServer = remoteConnection.options.streams;
for (let i = streamOptionsServer.length - 1; i >= 0; --i) {
if (streamOptionsServer[i].id === this.stream.streamId) {
streamOptionsServer.splice(i, 1);
}
}
}
}
}
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Defines the following events:
* - `streamPlaying`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) whenever its media stream starts playing (one of its videos has media
* and has begun to play). This event will be dispatched when these 3 conditions are met 1) The StreamManager has no video associated in the DOM 2) It is associated to one video 3) That video starts playing
* - `streamAudioVolumeChange`: dispatched by [[StreamManager]] ([[Publisher]] and [[Subscriber]]) when the volume of its Stream's audio track
* changes. Only applies if [[Stream.hasAudio]] is `true`. The frequency this event is fired with is defined by property `interval` of
* [[OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions]] (default 50ms)
*/
export class StreamManagerEvent extends Event {
/**
* For `streamAudioVolumeChange` event:
* - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume).
* They are not exact and depend on how the browser is managing the audio track, but -100 and 0 can be taken as limit values.
*
* For `streamPlaying` event undefined
*/
value: Object | undefined;
/**
* @hidden
*/
constructor(target: StreamManager, type: string, value: Object | undefined) {
super(false, target, type);
this.value = value;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,76 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Defines event `streamPropertyChanged` dispatched by [[Session]] as well as by [[StreamManager]] ([[Publisher]] and [[Subscriber]]).
* This event is fired when any remote stream (owned by a Subscriber) or local stream (owned by a Publisher) undergoes
* any change in any of its mutable properties (see [[changedProperty]]).
*/
export class StreamPropertyChangedEvent extends Event {
/**
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property [[Stream.connection]]
*/
stream: Stream;
/**
* The property of the stream that changed. This value is either `"videoActive"`, `"audioActive"`, `"videoDimensions"` or `"filter"`
*/
changedProperty: string;
/**
* Cause of the change on the stream's property:
* - For `videoActive`: `"publishVideo"`
* - For `audioActive`: `"publishAudio"`
* - For `videoDimensions`: `"deviceRotated"` or `"screenResized"`
*/
reason: string;
/**
* New value of the property (after change, current value)
*/
newValue: Object;
/**
* Previous value of the property (before change)
*/
oldValue: Object;
/**
* @hidden
*/
constructor(target: Session | StreamManager, stream: Stream, changedProperty: string, newValue: Object, oldValue: Object, reason: string) {
super(false, target, 'streamPropertyChanged');
this.stream = stream;
this.changedProperty = changedProperty;
this.newValue = newValue;
this.oldValue = oldValue;
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,49 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event } from './Event';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Defines the following events:
* - `videoElementCreated`: dispatched by [[Publisher]] and [[Subscriber]] whenever a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
* [Manage video players](/docs/how-do-i/manage-videos) section.
* - `videoElementDestroyed`: dispatched by [[Publisher]] and [[Subscriber]] whenever an HTML video element has been removed from DOM by OpenVidu Browser library.
*/
export class VideoElementEvent extends Event {
/**
* Video element that was created or destroyed
*/
element: HTMLVideoElement;
/**
* @hidden
*/
constructor(element: HTMLVideoElement, target: StreamManager, type: string) {
super(false, target, type);
this.element = element;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -0,0 +1,25 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { StreamOptionsServer } from './StreamOptionsServer';
export interface ConnectionOptions {
id: string;
createdAt: number;
metadata: string;
streams: StreamOptionsServer[];
}

View File

@ -0,0 +1,33 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Connection } from '../../../OpenVidu/Connection';
import { Filter } from '../../../OpenVidu/Filter';
export interface InboundStreamOptions {
id: string;
createdAt: number;
connection: Connection;
hasAudio: boolean;
hasVideo: boolean;
audioActive: boolean;
videoActive: boolean;
typeOfVideo: string;
frameRate: number;
videoDimensions: { width: number, height: number };
filter?: Filter;
}

View File

@ -0,0 +1,18 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
export interface ObjMap<T> { [s: string]: T; }

View File

@ -0,0 +1,23 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { PublisherProperties } from '../Public/PublisherProperties';
export interface OutboundStreamOptions {
publisherProperties: PublisherProperties;
mediaConstraints: MediaStreamConstraints;
}

View File

@ -0,0 +1,22 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
export interface SessionOptions {
sessionId: string;
participantId: string;
metadata: string;
}

View File

@ -0,0 +1,24 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Connection } from '../../../OpenVidu/Connection';
export interface SignalOptions {
type?: string;
to?: Connection[];
data?: string;
}

View File

@ -0,0 +1,31 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Filter } from '../../../OpenVidu/Filter';
export interface StreamOptionsServer {
id: string;
createdAt: number;
hasAudio: boolean;
hasVideo: boolean;
audioActive: boolean;
videoActive: boolean;
typeOfVideo: string;
frameRate: number;
videoDimensions: string;
filter: Filter;
}

View File

@ -0,0 +1,43 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
/**
* See [[Session.capabilities]]
*/
export interface Capabilities {
/**
* `true` if the client can call [[Session.forceDisconnect]], `false` if not
*/
forceDisconnect: boolean;
/**
* `true` if the client can call [[Session.forceUnpublish]], `false` if not
*/
forceUnpublish: boolean;
/**
* `true` if the client can call [[Session.publish]], `false` if not
*/
publish: boolean;
/**
* `true` if the client can call [[Session.subscribe]], `false` if not (true for every user for now)
*/
subscribe: boolean;
}

View File

@ -0,0 +1,37 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
/**
* See [[OpenVidu.getDevices]]
*/
export interface Device {
/**
* `"videoinput"`, `"audioinput"`
*/
kind: string;
/**
* Unique ID for the device. Use it on `audioSource` or `videoSource` properties of [[PublisherProperties]]
*/
deviceId: string;
/**
* Description of the device. An empty string if the user hasn't granted permissions to the site to access the device
*/
label: string;
}

View File

@ -0,0 +1,43 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Event as Event } from '../../Events/Event';
export interface EventDispatcher {
/**
* Adds function `handler` to handle event `type`
*
* @returns The EventDispatcher object
*/
on(type: string, handler: (event: Event) => void): EventDispatcher;
/**
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
*
* @returns The object that dispatched the event
*/
once(type: string, handler: (event: Event) => void): Object;
/**
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
*
* @returns The object that dispatched the event
*/
off(type: string, handler?: (event: Event) => void): Object;
}

View File

@ -0,0 +1,42 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
/**
* See [[OpenVidu.setAdvancedConfiguration]]
*/
export interface OpenViduAdvancedConfiguration {
/**
* Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server
* You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library).
*/
iceServers?: RTCIceServer[] | string;
/**
* URL to a custom screen share extension for Chrome (always based on ours: [openvidu-screen-sharing-chrome-extension](https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension)) to be used instead of the default one.
* Must be something like this: `https://chrome.google.com/webstore/detail/YOUR_WEBSTORE_EXTENSION_NAME/YOUR_EXTENSION_ID`
*/
screenShareChromeExtension?: string;
/**
* Custom configuration for the [[PublisherSpeakingEvent]] feature. It is an object which includes the following optional properties:
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started or stopped. Default **50** (ms)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_ and _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*/
publisherSpeakingEventsOptions?: any;
}

View File

@ -0,0 +1,92 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Filter } from '../../../OpenVidu/Filter';
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
/**
* See [[OpenVidu.initPublisher]]
*/
export interface PublisherProperties {
/**
* Which device should provide the audio source. Can be:
* - Property `deviceId` of a [[Device]]
* - A MediaStreamTrack obtained from a MediaStream object with [[OpenVidu.getUserMedia]]
* - `false` or null to have a video-only publisher
* @default _Default microphone_
*/
audioSource?: string | MediaStreamTrack | boolean;
/**
* Desired framerate of the video in frames per second.
* Limiting the framerate has always effect on browsers Chrome and Opera. Firefox requires that the input device explicitly supports the desired framerate.
* @default undefined
*/
frameRate?: number;
/**
* How the video element of the publisher should be inserted in the DOM
* @default VideoInsertMode.APPEND
*/
insertMode?: VideoInsertMode | string;
/**
* Whether the publisher's video will be mirrored in the page or not. Only affects the local view of the publisher in the browser (remote streams will not be mirrored). If `videoSource` is set to "screen" this property is fixed to `false`
* @default true
*/
mirror?: boolean;
/**
* Whether to initially publish to the session with the audio unmuted or muted. Only makes sense if property `audioSource` is NOT set to *false* or *null*. You can change the audio state later during the session with [[Publisher.publishAudio]]
* @default true
*/
publishAudio?: boolean;
/**
* Whether to initially publish to the session with the video enabled or disabled. Only makes sense if property `videoSource` is NOT set to *false* or *null*. You can change the video state later during the session with [[Publisher.publishVideo]]
* @default true
*/
publishVideo?: boolean;
/**
* Resolution of the video: `"320x240"`, `"640x480"`, `"1280x720"` (low, medium and high quality respectively)
* @default "640x480"
*/
resolution?: string;
/**
* Which device should provide the video source. Can be:
* - Property `deviceId` of a [[Device]]
* - `"screen"` to screen-share. We provide a default screen-shraring extension for Chrome that can run in any domain, but you can customize it so it has your own icon, your own name, etc. Visit this
* [GitHub repository](https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension/) to learn how. Once you have uploaded your own extension to Chrome Web Store,
* simply call `OpenVidu.setAdvancedConfiguration({screenShareChromeExtension : "https://chrome.google.com/webstore/detail/YOUR_EXTENSION_NAME/YOUR_EXTENSION_ID"})` before calling `OpenVidu.initPublisher(targetElement, {videoSource: "screen"})`.
* For Firefox `"screen"` string will ask for permissions to share the entire screen. To ask for a specific window or application, use `"window"` string instead (this only applies to Firefox).
* - A MediaStreamTrack obtained from a MediaStream object with [[OpenVidu.getUserMedia]]
* - `false` or null to have an audio-only publisher
* @default _Default camera_
*/
videoSource?: string | MediaStreamTrack | boolean;
/**
* **WARNING**: experimental option. This property may change in the near future
*
* Define a filter to apply in the Publisher's stream
*/
filter?: Filter;
}

View File

@ -0,0 +1,41 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { Connection } from '../../../OpenVidu/Connection';
/**
* See [[Session.signal]]
*/
export interface SignalOptions {
/**
* The actual message of the signal.
*/
data?: string;
/**
* The participants to whom to send the signal. They will only receive it if they are subscribed to
* event `Session.on('signal')`. If empty or undefined, the signal will be send to all participants.
*/
to?: Connection[];
/**
* The type of the signal. Participants subscribed to event `Session.on('signal:type')` will
* receive it. Participants subscribed to `Session.on('signal')` will receive all signals.
*/
type?: string;
}

View File

@ -0,0 +1,61 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { VideoInsertMode } from '../../Enums/VideoInsertMode';
export interface StreamManagerVideo {
/**
* DOM video element displaying the StreamManager's stream
*/
video: HTMLVideoElement;
/**
* `id` attribute of the DOM video element displaying the StreamManager's stream
*/
id: string;
/**
* The DOM HTMLElement assigned as target element when creating a video for the StreamManager. This property is defined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter.
* - [[StreamManager.createVideoElement]] has been called.
*
* This property is undefined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - [[StreamManager.addVideoElement]] has been called.
*/
targetElement?: HTMLElement;
/**
* How the DOM video element should be inserted with respect to `targetElement`. This property is defined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing a valid `targetElement` parameter.
* - [[StreamManager.createVideoElement]] has been called.
*
* This property is undefined when:
* - [[OpenVidu.initPublisher]] or [[Session.subscribe]] methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - [[StreamManager.addVideoElement]] has been called.
*/
insertMode?: VideoInsertMode;
/**
* @hidden
*/
canplayListenerAdded: boolean;
}

View File

@ -0,0 +1,43 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 { VideoInsertMode } from '../../Enums/VideoInsertMode';
/**
* See [[Session.subscribe]]
*/
export interface SubscriberProperties {
/**
* How the video element of the subscriber should be inserted in the DOM
* @default VideoInsertMode.APPEND
*/
insertMode?: VideoInsertMode | string;
/**
* Whether to initially subscribe to the audio track of the stream or not. You can change the audio state later with [[Subscriber.subscribeToAudio]]
* @default true
*/
subscribeToAudio?: boolean;
/**
* Whether to initially subscribe to the video track of the stream or not. You can change the video state later with [[Subscriber.subscribeToVideo]]
* @default true
*/
subscribeToVideo?: boolean;
}

View File

@ -0,0 +1,66 @@
function Mapper()
{
var sources = {};
this.forEach = function(callback)
{
for(var key in sources)
{
var source = sources[key];
for(var key2 in source)
callback(source[key2]);
};
};
this.get = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return undefined;
return ids[id];
};
this.remove = function(id, source)
{
var ids = sources[source];
if(ids == undefined)
return;
delete ids[id];
// Check it's empty
for(var i in ids){return false}
delete sources[source];
};
this.set = function(value, id, source)
{
if(value == undefined)
return this.remove(id, source);
var ids = sources[source];
if(ids == undefined)
sources[source] = ids = {};
ids[id] = value;
};
};
Mapper.prototype.pop = function(id, source)
{
var value = this.get(id, source);
if(value == undefined)
return undefined;
this.remove(id, source);
return value;
};
module.exports = Mapper;

View File

@ -0,0 +1,21 @@
/*
* (C) Copyright 2014 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.
*
*/
var JsonRpcClient = require('./jsonrpcclient');
exports.JsonRpcClient = JsonRpcClient;

View File

@ -0,0 +1,292 @@
/*
* (C) Copyright 2014 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.
*
*/
var RpcBuilder = require('../');
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
Date.now = Date.now || function () {
return +new Date;
};
var PING_INTERVAL = 5000;
var RECONNECTING = 'RECONNECTING';
var CONNECTED = 'CONNECTED';
var DISCONNECTED = 'DISCONNECTED';
var Logger = console;
/**
*
* heartbeat: interval in ms for each heartbeat message,
* sendCloseMessage : true / false, before closing the connection, it sends a closeSession message
* <pre>
* ws : {
* uri : URI to conntect to,
* useSockJS : true (use SockJS) / false (use WebSocket) by default,
* onconnected : callback method to invoke when connection is successful,
* ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached),
* onreconnecting : callback method to invoke when the client is reconnecting,
* onreconnected : callback method to invoke when the client successfully reconnects,
* onerror : callback method to invoke when there is an error
* },
* rpc : {
* requestTimeout : timeout for a request,
* sessionStatusChanged: callback method for changes in session status,
* mediaRenegotiation: mediaRenegotiation
* }
* </pre>
*/
function JsonRpcClient(configuration) {
var self = this;
var wsConfig = configuration.ws;
var notReconnectIfNumLessThan = -1;
var pingNextNum = 0;
var enabledPings = true;
var pingPongStarted = false;
var pingInterval;
var status = DISCONNECTED;
var onreconnecting = wsConfig.onreconnecting;
var onreconnected = wsConfig.onreconnected;
var onconnected = wsConfig.onconnected;
var onerror = wsConfig.onerror;
configuration.rpc.pull = function (params, request) {
request.reply(null, "push");
}
wsConfig.onreconnecting = function () {
Logger.debug("--------- ONRECONNECTING -----------");
if (status === RECONNECTING) {
Logger.error("Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it");
return;
}
stopPing();
status = RECONNECTING;
if (onreconnecting) {
onreconnecting();
}
}
wsConfig.onreconnected = function () {
Logger.debug("--------- ONRECONNECTED -----------");
if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
updateNotReconnectIfLessThan();
if (onreconnected) {
onreconnected();
}
}
wsConfig.onconnected = function () {
Logger.debug("--------- ONCONNECTED -----------");
if (status === CONNECTED) {
Logger.error("Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it");
return;
}
status = CONNECTED;
enabledPings = true;
usePing();
if (onconnected) {
onconnected();
}
}
wsConfig.onerror = function (error) {
Logger.debug("--------- ONERROR -----------");
status = DISCONNECTED;
stopPing();
if (onerror) {
onerror(error);
}
}
var ws = new WebSocketWithReconnection(wsConfig);
Logger.debug('Connecting websocket to URI: ' + wsConfig.uri);
var rpcBuilderOptions = {
request_timeout: configuration.rpc.requestTimeout,
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
};
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws,
function (request) {
Logger.debug('Received request: ' + JSON.stringify(request));
try {
var func = configuration.rpc[request.method];
if (func === undefined) {
Logger.error("Method " + request.method + " not registered in client");
} else {
func(request.params, request);
}
} catch (err) {
Logger.error('Exception processing request: ' + JSON.stringify(request));
Logger.error(err);
}
});
this.send = function (method, params, callback) {
if (method !== 'ping') {
Logger.debug('Request: method:' + method + " params:" + JSON.stringify(params));
}
var requestTime = Date.now();
rpc.encode(method, params, function (error, result) {
if (error) {
try {
Logger.error("ERROR:" + error.message + " in Request: method:" +
method + " params:" + JSON.stringify(params) + " request:" +
error.request);
if (error.data) {
Logger.error("ERROR DATA:" + JSON.stringify(error.data));
}
} catch (e) {}
error.requestTime = requestTime;
}
if (callback) {
if (result != undefined && result.value !== 'pong') {
Logger.debug('Response: ' + JSON.stringify(result));
}
callback(error, result);
}
});
}
function updateNotReconnectIfLessThan() {
Logger.debug("notReconnectIfNumLessThan = " + pingNextNum + ' (old=' +
notReconnectIfNumLessThan + ')');
notReconnectIfNumLessThan = pingNextNum;
}
function sendPing() {
if (enabledPings) {
var params = null;
if (pingNextNum == 0 || pingNextNum == notReconnectIfNumLessThan) {
params = {
interval: configuration.heartbeat || PING_INTERVAL
};
}
pingNextNum++;
self.send('ping', params, (function (pingNum) {
return function (error, result) {
if (error) {
Logger.debug("Error in ping request #" + pingNum + " (" +
error.message + ")");
if (pingNum > notReconnectIfNumLessThan) {
enabledPings = false;
updateNotReconnectIfLessThan();
Logger.debug("Server did not respond to ping message #" +
pingNum + ". Reconnecting... ");
ws.reconnectWs();
}
}
}
})(pingNextNum));
} else {
Logger.debug("Trying to send ping, but ping is not enabled");
}
}
/*
* If configuration.hearbeat has any value, the ping-pong will work with the interval
* of configuration.hearbeat
*/
function usePing() {
if (!pingPongStarted) {
Logger.debug("Starting ping (if configured)")
pingPongStarted = true;
if (configuration.heartbeat != undefined) {
pingInterval = setInterval(sendPing, configuration.heartbeat);
sendPing();
}
}
}
function stopPing() {
clearInterval(pingInterval);
pingPongStarted = false;
enabledPings = false;
pingNextNum = -1;
rpc.cancel();
}
this.close = function (code, reason) {
Logger.debug("Closing with code: " + code + " because: " + reason);
if (pingInterval != undefined) {
Logger.debug("Clearing ping interval");
clearInterval(pingInterval);
}
pingPongStarted = false;
enabledPings = false;
if (configuration.sendCloseMessage) {
Logger.debug("Sending close message")
this.send('closeSession', null, function (error, result) {
if (error) {
Logger.error("Error sending close message: " + JSON.stringify(error));
}
ws.close(code, reason);
});
} else {
ws.close(code, reason);
}
}
// This method is only for testing
this.forceClose = function (millis) {
ws.forceClose(millis);
}
this.reconnect = function () {
ws.reconnectWs();
}
this.resetPing = function () {
enabledPings = true;
pingNextNum = 0;
usePing();
}
}
module.exports = JsonRpcClient;

View File

@ -0,0 +1,21 @@
/*
* (C) Copyright 2014 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.
*
*/
var WebSocketWithReconnection = require('./webSocketWithReconnection');
exports.WebSocketWithReconnection = WebSocketWithReconnection;

View File

@ -0,0 +1,242 @@
/*
* (C) Copyright 2013-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.
*/
"use strict";
var BrowserWebSocket = global.WebSocket || global.MozWebSocket;
var Logger = console;
/**
* Get either the `WebSocket` or `MozWebSocket` globals
* in the browser or try to resolve WebSocket-compatible
* interface exposed by `ws` for Node-like environment.
*/
/*var WebSocket = BrowserWebSocket;
if (!WebSocket && typeof window === 'undefined') {
try {
WebSocket = require('ws');
} catch (e) { }
}*/
//var SockJS = require('sockjs-client');
var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
/*
config = {
uri : wsUri,
useSockJS : true (use SockJS) / false (use WebSocket) by default,
onconnected : callback method to invoke when connection is successful,
ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached),
onreconnecting : callback method to invoke when the client is reconnecting,
onreconnected : callback method to invoke when the client successfully reconnects,
};
*/
function WebSocketWithReconnection(config) {
var closing = false;
var registerMessageHandler;
var wsUri = config.uri;
var useSockJS = config.useSockJS;
var reconnecting = false;
var forcingDisconnection = false;
var ws;
if (useSockJS) {
ws = new SockJS(wsUri);
} else {
ws = new WebSocket(wsUri);
}
ws.onopen = function () {
logConnected(ws, wsUri);
if (config.onconnected) {
config.onconnected();
}
};
ws.onerror = function (error) {
Logger.error("Could not connect to " + wsUri + " (invoking onerror if defined)", error);
if (config.onerror) {
config.onerror(error);
}
};
function logConnected(ws, wsUri) {
try {
Logger.debug("WebSocket connected to " + wsUri);
} catch (e) {
Logger.error(e);
}
}
var reconnectionOnClose = function () {
if (ws.readyState === CLOSED) {
if (closing) {
Logger.debug("Connection closed by user");
} else {
Logger.debug("Connection closed unexpectecly. Reconnecting...");
reconnectToSameUri(MAX_RETRIES, 1);
}
} else {
Logger.debug("Close callback from previous websocket. Ignoring it");
}
};
ws.onclose = reconnectionOnClose;
function reconnectToSameUri(maxRetries, numRetries) {
Logger.debug("reconnectToSameUri (attempt #" + numRetries + ", max=" + maxRetries + ")");
if (numRetries === 1) {
if (reconnecting) {
Logger.warn("Trying to reconnectToNewUri when reconnecting... Ignoring this reconnection.")
return;
} else {
reconnecting = true;
}
if (config.onreconnecting) {
config.onreconnecting();
}
}
if (forcingDisconnection) {
reconnectToNewUri(maxRetries, numRetries, wsUri);
} else {
if (config.newWsUriOnReconnection) {
config.newWsUriOnReconnection(function (error, newWsUri) {
if (error) {
Logger.debug(error);
setTimeout(function () {
reconnectToSameUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
} else {
reconnectToNewUri(maxRetries, numRetries, newWsUri);
}
})
} else {
reconnectToNewUri(maxRetries, numRetries, wsUri);
}
}
}
// TODO Test retries. How to force not connection?
function reconnectToNewUri(maxRetries, numRetries, reconnectWsUri) {
Logger.debug("Reconnection attempt #" + numRetries);
ws.close();
wsUri = reconnectWsUri || wsUri;
var newWs;
if (useSockJS) {
newWs = new SockJS(wsUri);
} else {
newWs = new WebSocket(wsUri);
}
newWs.onopen = function () {
Logger.debug("Reconnected after " + numRetries + " attempts...");
logConnected(newWs, wsUri);
reconnecting = false;
registerMessageHandler();
if (config.onreconnected()) {
config.onreconnected();
}
newWs.onclose = reconnectionOnClose;
};
var onErrorOrClose = function (error) {
Logger.warn("Reconnection error: ", error);
if (numRetries === maxRetries) {
if (config.ondisconnect) {
config.ondisconnect();
}
} else {
setTimeout(function () {
reconnectToSameUri(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
}
};
newWs.onerror = onErrorOrClose;
ws = newWs;
}
this.close = function () {
closing = true;
ws.close();
};
// This method is only for testing
this.forceClose = function (millis) {
Logger.debug("Testing: Force WebSocket close");
if (millis) {
Logger.debug("Testing: Change wsUri for " + millis + " millis to simulate net failure");
var goodWsUri = wsUri;
wsUri = "wss://21.234.12.34.4:443/";
forcingDisconnection = true;
setTimeout(function () {
Logger.debug("Testing: Recover good wsUri " + goodWsUri);
wsUri = goodWsUri;
forcingDisconnection = false;
}, millis);
}
ws.close();
};
this.reconnectWs = function () {
Logger.debug("reconnectWs");
reconnectToSameUri(MAX_RETRIES, 1);
};
this.send = function (message) {
ws.send(message);
};
this.addEventListener = function (type, callback) {
registerMessageHandler = function () {
ws.addEventListener(type, callback);
};
registerMessageHandler();
};
}
module.exports = WebSocketWithReconnection;

View File

@ -0,0 +1,822 @@
/*
* (C) Copyright 2014 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.
*
*/
var defineProperty_IE8 = false
if(Object.defineProperty)
{
try
{
Object.defineProperty({}, "x", {});
}
catch(e)
{
defineProperty_IE8 = true
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function(oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function() {},
fBound = function() {
return fToBind.apply(this instanceof fNOP && oThis
? this
: oThis,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');
var packers = require('./packers');
var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods)
{
if(!responseMethods) return {};
for(var key in responseMethods)
{
var value = responseMethods[key];
if(typeof value == 'string')
responseMethods[key] =
{
response: value
}
};
return responseMethods;
};
function unifyTransport(transport)
{
if(!transport) return;
// Transport as a function
if(transport instanceof Function)
return {send: transport};
// WebSocket & DataChannel
if(transport.send instanceof Function)
return transport;
// Message API (Inter-window & WebWorker)
if(transport.postMessage instanceof Function)
{
transport.send = transport.postMessage;
return transport;
}
// Stream API
if(transport.write instanceof Function)
{
transport.send = transport.write;
return transport;
}
// Transports that only can receive messages, but not send
if(transport.onmessage !== undefined) return;
if(transport.pause instanceof Function) return;
throw new SyntaxError("Transport is not a function nor a valid object");
};
/**
* Representation of a RPC notification
*
* @class
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
*/
function RpcNotification(method, params)
{
if(defineProperty_IE8)
{
this.method = method
this.params = params
}
else
{
Object.defineProperty(this, 'method', {value: method, enumerable: true});
Object.defineProperty(this, 'params', {value: params, enumerable: true});
}
};
/**
* @class
*
* @constructor
*
* @param {object} packer
*
* @param {object} [options]
*
* @param {object} [transport]
*
* @param {Function} [onRequest]
*/
function RpcBuilder(packer, options, transport, onRequest)
{
var self = this;
if(!packer)
throw new SyntaxError('Packer is not defined');
if(!packer.pack || !packer.unpack)
throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods);
if(options instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options;
transport = undefined;
options = undefined;
};
if(options && options.send instanceof Function)
{
if(transport && !(transport instanceof Function))
throw new SyntaxError("Only a function can be after transport");
onRequest = transport;
transport = options;
options = undefined;
};
if(transport instanceof Function)
{
if(onRequest != undefined)
throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport;
transport = undefined;
};
if(transport && transport.send instanceof Function)
if(onRequest && !(onRequest instanceof Function))
throw new SyntaxError("Only a function can be after transport");
options = options || {};
EventEmitter.call(this);
if(onRequest)
this.on('request', onRequest);
if(defineProperty_IE8)
this.peerID = options.peerID
else
Object.defineProperty(this, 'peerID', {value: options.peerID});
var max_retries = options.max_retries || 0;
function transportMessage(event)
{
self.decode(event.data || event);
};
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
// Remove listener from old transport
if(transport)
{
// W3C transports
if(transport.removeEventListener)
transport.removeEventListener('message', transportMessage);
// Node.js Streams API
else if(transport.removeListener)
transport.removeListener('data', transportMessage);
};
// Set listener on new transport
if(value)
{
// W3C transports
if(value.addEventListener)
value.addEventListener('message', transportMessage);
// Node.js Streams API
else if(value.addListener)
value.addListener('data', transportMessage);
};
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
this.setTransport(transport);
var request_timeout = options.request_timeout || BASE_TIMEOUT;
var ping_request_timeout = options.ping_request_timeout || request_timeout;
var response_timeout = options.response_timeout || BASE_TIMEOUT;
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
var requestID = 0;
var requests = new Mapper();
var responses = new Mapper();
var processedResponses = new Mapper();
var message2Key = {};
/**
* Store the response to prevent to process duplicate request later
*/
function storeResponse(message, id, dest)
{
var response =
{
message: message,
/** Timeout to auto-clean old responses */
timeout: setTimeout(function()
{
responses.remove(id, dest);
},
response_timeout)
};
responses.set(response, id, dest);
};
/**
* Store the response to ignore duplicated messages later
*/
function storeProcessedResponse(ack, from)
{
var timeout = setTimeout(function()
{
processedResponses.remove(ack, from);
},
duplicates_timeout);
processedResponses.set(timeout, ack, from);
};
/**
* Representation of a RPC request
*
* @class
* @extends RpcNotification
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param {Integer} id - identifier of the request
* @param [from] - source of the notification
*/
function RpcRequest(method, params, id, from, transport)
{
RpcNotification.call(this, method, params);
this.getTransport = function()
{
return transport;
}
this.setTransport = function(value)
{
transport = unifyTransport(value);
}
if(!defineProperty_IE8)
Object.defineProperty(this, 'transport',
{
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
})
var response = responses.get(id, from);
/**
* @constant {Boolean} duplicated
*/
if(!(transport || self.getTransport()))
{
if(defineProperty_IE8)
this.duplicated = Boolean(response)
else
Object.defineProperty(this, 'duplicated',
{
value: Boolean(response)
});
}
var responseMethod = responseMethods[method];
this.pack = packer.pack.bind(packer, this, id)
/**
* Generate a response to this request
*
* @param {Error} [error]
* @param {*} [result]
*
* @returns {string}
*/
this.reply = function(error, result, transport)
{
// Fix optional parameters
if(error instanceof Function || error && error.send instanceof Function)
{
if(result != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = error;
result = null;
error = undefined;
}
else if(result instanceof Function
|| result && result.send instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
transport = result;
result = null;
};
transport = unifyTransport(transport);
// Duplicated request, remove old response timeout
if(response)
clearTimeout(response.timeout);
if(from != undefined)
{
if(error)
error.dest = from;
if(result)
result.dest = from;
};
var message;
// New request or overriden one, create new response with provided data
if(error || result != undefined)
{
if(self.peerID != undefined)
{
if(error)
error.from = self.peerID;
else
result.from = self.peerID;
}
// Protocol indicates that responses has own request methods
if(responseMethod)
{
if(responseMethod.error == undefined && error)
message =
{
error: error
};
else
{
var method = error
? responseMethod.error
: responseMethod.response;
message =
{
method: method,
params: error || result
};
}
}
else
message =
{
error: error,
result: result
};
message = packer.pack(message, id);
}
// Duplicate & not-overriden request, re-send old response
else if(response)
message = response.message;
// New empty reply, response null value
else
message = packer.pack({result: null}, id);
// Store the response to prevent to process a duplicated request later
storeResponse(message, id, from);
// Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport();
if(transport)
return transport.send(message);
return message;
}
};
inherits(RpcRequest, RpcNotification);
function cancel(message)
{
var key = message2Key[message];
if(!key) return;
delete message2Key[message];
var request = requests.pop(key.id, key.dest);
if(!request) return;
clearTimeout(request.timeout);
// Start duplicated responses timeout
storeProcessedResponse(key.id, key.dest);
};
/**
* Allow to cancel a request and don't wait for a response
*
* If `message` is not given, cancel all the request
*/
this.cancel = function(message)
{
if(message) return cancel(message);
for(var message in message2Key)
cancel(message);
};
this.close = function()
{
// Prevent to receive new messages
var transport = this.getTransport();
if(transport && transport.close)
transport.close(4003, "Cancel request");
// Request & processed responses
this.cancel();
processedResponses.forEach(clearTimeout);
// Responses
responses.forEach(function(response)
{
clearTimeout(response.timeout);
});
};
/**
* Generates and encode a JsonRPC 2.0 message
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param [dest] - destination of the notification
* @param {object} [transport] - transport where to send the message
* @param [callback] - function called when a response to this request is
* received. If not defined, a notification will be send instead
*
* @returns {string} A raw JsonRPC 2.0 request or notification string
*/
this.encode = function(method, params, dest, transport, callback)
{
// Fix optional parameters
if(params instanceof Function)
{
if(dest != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = params;
transport = undefined;
dest = undefined;
params = undefined;
}
else if(dest instanceof Function)
{
if(transport != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = dest;
transport = undefined;
dest = undefined;
}
else if(transport instanceof Function)
{
if(callback != undefined)
throw new SyntaxError("There can't be parameters after callback");
callback = transport;
transport = undefined;
};
if(self.peerID != undefined)
{
params = params || {};
params.from = self.peerID;
};
if(dest != undefined)
{
params = params || {};
params.dest = dest;
};
// Encode message
var message =
{
method: method,
params: params
};
if(callback)
{
var id = requestID++;
var retried = 0;
message = packer.pack(message, id);
function dispatchCallback(error, result)
{
self.cancel(message);
callback(error, result);
};
var request =
{
message: message,
callback: dispatchCallback,
responseMethods: responseMethods[method] || {}
};
var encode_transport = unifyTransport(transport);
function sendRequest(transport)
{
var rt = (method === 'ping' ? ping_request_timeout : request_timeout);
request.timeout = setTimeout(timeout, rt*Math.pow(2, retried++));
message2Key[message] = {id: id, dest: dest};
requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport();
if(transport)
return transport.send(message);
return message;
};
function retry(transport)
{
transport = unifyTransport(transport);
console.warn(retried+' retry for request message:',message);
var timeout = processedResponses.pop(id, dest);
clearTimeout(timeout);
return sendRequest(transport);
};
function timeout()
{
if(retried < max_retries)
return retry(transport);
var error = new Error('Request has timed out');
error.request = message;
error.retry = retry;
dispatchCallback(error)
};
return sendRequest(transport);
};
// Return the packed message
message = packer.pack(message);
transport = transport || this.getTransport();
if(transport)
return transport.send(message);
return message;
};
/**
* Decode and process a JsonRPC 2.0 message
*
* @param {string} message - string with the content of the message
*
* @returns {RpcNotification|RpcRequest|undefined} - the representation of the
* notification or the request. If a response was processed, it will return
* `undefined` to notify that it was processed
*
* @throws {TypeError} - Message is not defined
*/
this.decode = function(message, transport)
{
if(!message)
throw new TypeError("Message is not defined");
try
{
message = packer.unpack(message);
}
catch(e)
{
// Ignore invalid messages
return console.debug(e, message);
};
var id = message.id;
var ack = message.ack;
var method = message.method;
var params = message.params || {};
var from = params.from;
var dest = params.dest;
// Ignore messages send by us
if(self.peerID != undefined && from == self.peerID) return;
// Notification
if(id == undefined && ack == undefined)
{
var notification = new RpcNotification(method, params);
if(self.emit('request', notification)) return;
return notification;
};
function processRequest()
{
// If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport();
if(transport)
{
var response = responses.get(id, from);
if(response)
return transport.send(response.message);
};
var idAck = (id != undefined) ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport);
if(self.emit('request', request)) return;
return request;
};
function processResponse(request, error, result)
{
request.callback(error, result);
};
function duplicatedResponse(timeout)
{
console.warn("Response already processed", message);
// Update duplicated responses timeout
clearTimeout(timeout);
storeProcessedResponse(ack, from);
};
// Request, or response with own method
if(method)
{
// Check if it's a response with own method
if(dest == undefined || dest == self.peerID)
{
var request = requests.get(ack, from);
if(request)
{
var responseMethods = request.responseMethods;
if(method == responseMethods.error)
return processResponse(request, params);
if(method == responseMethods.response)
return processResponse(request, null, params);
return processRequest();
}
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
}
// Request
return processRequest();
};
var error = message.error;
var result = message.result;
// Ignore responses not send to us
if(error && error.dest && error.dest != self.peerID) return;
if(result && result.dest && result.dest != self.peerID) return;
// Response
var request = requests.get(ack, from);
if(!request)
{
var processed = processedResponses.get(ack, from);
if(processed)
return duplicatedResponse(processed);
return console.warn("No callback was defined for this message", message);
};
// Process response
processResponse(request, error, result);
};
};
inherits(RpcBuilder, EventEmitter);
RpcBuilder.RpcNotification = RpcNotification;
module.exports = RpcBuilder;
var clients = require('./clients');
var transports = require('./clients/transports');
RpcBuilder.clients = clients;
RpcBuilder.clients.transports = transports;
RpcBuilder.packers = packers;

View File

@ -0,0 +1,103 @@
/**
* JsonRPC 2.0 packer
*/
/**
* Pack a JsonRPC 2.0 message
*
* @param {Object} message - object to be packaged. It requires to have all the
* fields needed by the JsonRPC 2.0 message that it's going to be generated
*
* @return {String} - the stringified JsonRPC 2.0 message
*/
function pack(message, id)
{
var result =
{
jsonrpc: "2.0"
};
// Request
if(message.method)
{
result.method = message.method;
if(message.params)
result.params = message.params;
// Request is a notification
if(id != undefined)
result.id = id;
}
// Response
else if(id != undefined)
{
if(message.error)
{
if(message.result !== undefined)
throw new TypeError("Both result and error are defined");
result.error = message.error;
}
else if(message.result !== undefined)
result.result = message.result;
else
throw new TypeError("No result or error is defined");
result.id = id;
};
return JSON.stringify(result);
};
/**
* Unpack a JsonRPC 2.0 message
*
* @param {String} message - string with the content of the JsonRPC 2.0 message
*
* @throws {TypeError} - Invalid JsonRPC version
*
* @return {Object} - object filled with the JsonRPC 2.0 message content
*/
function unpack(message)
{
var result = message;
if(typeof message === 'string' || message instanceof String) {
result = JSON.parse(message);
}
// Check if it's a valid message
var version = result.jsonrpc;
if(version !== '2.0')
throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response
if(result.method == undefined)
{
if(result.id == undefined)
throw new TypeError("Invalid message: "+message);
var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none
if(result_defined && error_defined)
throw new TypeError("Both result and error are defined: "+message);
if(!result_defined && !error_defined)
throw new TypeError("No result or error is defined: "+message);
result.ack = result.id;
delete result.id;
}
// Return unpacked message
return result;
};
exports.pack = pack;
exports.unpack = unpack;

View File

@ -0,0 +1,13 @@
function pack(message)
{
throw new TypeError("Not yet implemented");
};
function unpack(message)
{
throw new TypeError("Not yet implemented");
};
exports.pack = pack;
exports.unpack = unpack;

View File

@ -0,0 +1,6 @@
var JsonRPC = require('./JsonRPC');
var XmlRPC = require('./XmlRPC');
exports.JsonRPC = JsonRPC;
exports.XmlRPC = XmlRPC;

View File

@ -0,0 +1,220 @@
// Last time updated on June 08, 2018
// Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/getScreenId.
// ______________
// getScreenId.js
/*
getScreenId(function (error, sourceId, screen_constraints) {
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(microsoftEdge) {
navigator.getDisplayMedia(screen_constraints).then(onSuccess, onFailure);
}
else {
navigator.mediaDevices.getUserMedia(screen_constraints).then(onSuccess)catch(onFailure);
}
}, 'pass second parameter only if you want system audio');
*/
window.getScreenId = function (firefoxString, callback, custom_parameter) {
if (navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob)) {
// microsoft edge => navigator.getDisplayMedia(screen_constraints).then(onSuccess, onFailure);
callback({
video: true
});
return;
}
// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
callback(null, 'firefox', {
video: {
mozMediaSource: firefoxString,
mediaSource: firefoxString
}
});
return;
}
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied');
} else {
callback(null, event.data.chromeMediaSourceId, getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack));
}
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
}
if (!custom_parameter) {
setTimeout(postGetSourceIdMessage, 100);
}
else {
setTimeout(function () {
postGetSourceIdMessage(custom_parameter);
}, 100);
}
};
function getScreenConstraints(error, sourceId, canRequestAudioTrack) {
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
maxWidth: window.screen.width > 1920 ? window.screen.width : 1920,
maxHeight: window.screen.height > 1080 ? window.screen.height : 1080
},
optional: []
}
};
if (!!canRequestAudioTrack) {
screen_constraints.audio = {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
// echoCancellation: true
},
optional: []
};
}
if (sourceId) {
screen_constraints.video.mandatory.chromeMediaSourceId = sourceId;
if (screen_constraints.audio && screen_constraints.audio.mandatory) {
screen_constraints.audio.mandatory.chromeMediaSourceId = sourceId;
}
}
return screen_constraints;
}
function postGetSourceIdMessage(custom_parameter) {
if (!iframe) {
loadIFrame(function () {
postGetSourceIdMessage(custom_parameter);
});
return;
}
if (!iframe.isLoaded) {
setTimeout(function () {
postGetSourceIdMessage(custom_parameter);
}, 100);
return;
}
if (!custom_parameter) {
iframe.contentWindow.postMessage({
captureSourceId: true
}, '*');
}
else if (!!custom_parameter.forEach) {
iframe.contentWindow.postMessage({
captureCustomSourceId: custom_parameter
}, '*');
}
else {
iframe.contentWindow.postMessage({
captureSourceIdWithAudio: true
}, '*');
}
}
var iframe;
// this function is used in RTCMultiConnection v3
window.getScreenConstraints = function (callback) {
loadIFrame(function () {
getScreenId(function (error, sourceId, screen_constraints) {
if (!screen_constraints) {
screen_constraints = {
video: true
};
}
callback(error, screen_constraints.video);
});
});
};
function loadIFrame(loadCallback) {
if (iframe) {
loadCallback();
return;
}
iframe = document.createElement('iframe');
iframe.onload = function () {
iframe.isLoaded = true;
loadCallback();
};
iframe.src = 'https://openvidu.github.io/openvidu-screen-sharing-chrome-extension/';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
}
window.getChromeExtensionStatus = function (callback) {
// for Firefox:
if (!!navigator.mozGetUserMedia) {
callback('installed-enabled');
return;
}
window.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus);
// this event listener is no more needed
window.removeEventListener('message', onIFrameCallback);
}
}
setTimeout(postGetChromeExtensionStatusMessage, 100);
};
function postGetChromeExtensionStatusMessage() {
if (!iframe) {
loadIFrame(postGetChromeExtensionStatusMessage);
return;
}
if (!iframe.isLoaded) {
setTimeout(postGetChromeExtensionStatusMessage, 100);
return;
}
iframe.contentWindow.postMessage({
getChromeExtensionStatus: true
}, '*');
}
exports.getScreenId = getScreenId;

View File

@ -0,0 +1,164 @@
// global variables
var chromeMediaSource = 'screen';
var sourceId;
var screenCallback;
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !isOpera;
window.addEventListener('message', function (event) {
if (event.origin != window.location.origin) {
return;
}
onMessageCallback(event.data);
});
// and the function that handles received messages
function onMessageCallback(data) {
// "cancel" button is clicked
if (data == 'PermissionDeniedError') {
if (screenCallback)
return screenCallback('PermissionDeniedError');
else
throw new Error('PermissionDeniedError');
}
// extension notified his presence
if (data == 'rtcmulticonnection-extension-loaded') {
chromeMediaSource = 'desktop';
}
// extension shared temp sourceId
if (data.sourceId && screenCallback) {
screenCallback(sourceId = data.sourceId, data.canRequestAudioTrack === true);
}
}
// this method can be used to check if chrome extension is installed & enabled.
function isChromeExtensionAvailable(callback) {
if (!callback) return;
if (chromeMediaSource == 'desktop') return callback(true);
// ask extension if it is available
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback(false);
} else callback(true);
}, 2000);
}
// this function can be used to get "source-id" from the extension
function getSourceId(callback) {
if (!callback)
throw '"callback" parameter is mandatory.';
if (sourceId)
return callback(sourceId);
screenCallback = callback;
window.postMessage('get-sourceId', '*');
}
// this function can be used to get "source-id" from the extension
function getCustomSourceId(arr, callback) {
if (!arr || !arr.forEach) throw '"arr" parameter is mandatory and it must be an array.';
if (!callback) throw '"callback" parameter is mandatory.';
if (sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage({
'get-custom-sourceId': arr
}, '*');
}
// this function can be used to get "source-id" from the extension
function getSourceIdWithAudio(callback) {
if (!callback) throw '"callback" parameter is mandatory.';
if (sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage('audio-plus-tab', '*');
}
function getChromeExtensionStatus(extensionid, callback) {
if (isFirefox)
return callback('not-chrome');
if (arguments.length != 2) {
callback = extensionid;
extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id
}
var image = document.createElement('img');
image.src = 'chrome-extension://' + extensionid + '/icon.png';
image.onload = function () {
chromeMediaSource = 'screen';
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback('installed-disabled');
} else
callback('installed-enabled');
}, 2000);
};
image.onerror = function () {
callback('not-installed');
};
}
function getScreenConstraintsWithAudio(callback) {
getScreenConstraints(callback, true);
}
// this function explains how to use above methods/objects
function getScreenConstraints(callback, captureSourceIdWithAudio) {
sourceId = '';
var firefoxScreenConstraints = {
mozMediaSource: 'window',
mediaSource: 'window'
};
if (isFirefox)
return callback(null, firefoxScreenConstraints);
// this statement defines getUserMedia constraints
// that will be used to capture content of screen
var screen_constraints = {
mandatory: {
chromeMediaSource: chromeMediaSource,
maxWidth: screen.width > 1920 ? screen.width : 1920,
maxHeight: screen.height > 1080 ? screen.height : 1080
},
optional: []
};
// this statement verifies chrome extension availability
// if installed and available then it will invoke extension API
// otherwise it will fallback to command-line based screen capturing API
if (chromeMediaSource == 'desktop' && !sourceId) {
if (captureSourceIdWithAudio) {
getSourceIdWithAudio(function (sourceId, canRequestAudioTrack) {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
if (canRequestAudioTrack) {
screen_constraints.canRequestAudioTrack = true;
}
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
}
else {
getSourceId(function (sourceId) {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
}
return;
}
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
if (chromeMediaSource == 'desktop') {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
}
// now invoking native getUserMedia API
callback(null, screen_constraints);
}
exports.getScreenConstraints = getScreenConstraints;
exports.getScreenConstraintsWithAudio = getScreenConstraintsWithAudio;
exports.isChromeExtensionAvailable = isChromeExtensionAvailable;
exports.getChromeExtensionStatus = getChromeExtensionStatus;
exports.getSourceId = getSourceId;

View File

@ -0,0 +1,315 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 freeice = require('freeice');
import uuid = require('uuid');
import platform = require('platform');
export interface WebRtcPeerConfiguration {
mediaConstraints: {
audio: boolean,
video: boolean
};
simulcast: boolean;
onicecandidate: (event) => void;
iceServers: RTCIceServer[] | undefined;
mediaStream?: MediaStream;
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
id?: string;
}
export class WebRtcPeer {
pc: RTCPeerConnection;
id: string;
remoteCandidatesQueue: RTCIceCandidate[] = [];
localCandidatesQueue: RTCIceCandidate[] = [];
iceCandidateList: RTCIceCandidate[] = [];
private candidategatheringdone = false;
constructor(private configuration: WebRtcPeerConfiguration) {
this.configuration.iceServers = (!!this.configuration.iceServers && this.configuration.iceServers.length > 0) ? this.configuration.iceServers : freeice();
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
this.id = !!configuration.id ? configuration.id : uuid.v4();
this.pc.onicecandidate = event => {
if (!!event.candidate) {
const candidate: RTCIceCandidate = event.candidate;
if (candidate) {
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate });
this.candidategatheringdone = false;
this.configuration.onicecandidate(event.candidate);
} else if (!this.candidategatheringdone) {
this.candidategatheringdone = true;
}
}
};
this.pc.onsignalingstatechange = () => {
if (this.pc.signalingState === 'stable') {
while (this.iceCandidateList.length > 0) {
this.pc.addIceCandidate(<RTCIceCandidate>this.iceCandidateList.shift());
}
}
};
this.start();
}
/**
* This function creates the RTCPeerConnection object taking into account the
* properties received in the constructor. It starts the SDP negotiation
* process: generates the SDP offer and invokes the onsdpoffer callback. This
* callback is expected to send the SDP offer, in order to obtain an SDP
* answer from another peer.
*/
start(): Promise<any> {
return new Promise((resolve, reject) => {
if (this.pc.signalingState === 'closed') {
reject('The peer connection object is in "closed" state. This is most likely due to an invocation of the dispose method before accepting in the dialogue');
}
if (!!this.configuration.mediaStream) {
if (platform['isIonicIos']) {
// iOS Ionic. LIMITATION: must use deprecated WebRTC API
const pc2: any = this.pc;
pc2.addStream(this.configuration.mediaStream);
} else {
for (const track of this.configuration.mediaStream.getTracks()) {
this.pc.addTrack(track, this.configuration.mediaStream);
}
}
resolve();
}
});
}
/**
* This method frees the resources used by WebRtcPeer
*/
dispose(videoSourceIsMediaStreamTrack: boolean) {
console.debug('Disposing WebRtcPeer');
try {
if (this.pc) {
if (this.pc.signalingState === 'closed') {
return;
}
this.remoteCandidatesQueue = [];
this.localCandidatesQueue = [];
if (platform['isIonicIos']) {
// iOS Ionic. LIMITATION: must use deprecated WebRTC API
// Stop senders deprecated
const pc1: any = this.pc;
for (const sender of pc1.getLocalStreams()) {
if (!videoSourceIsMediaStreamTrack) {
sender.stop();
}
pc1.removeStream(sender);
}
// Stop receivers deprecated
for (const receiver of pc1.getRemoteStreams()) {
if (!!receiver.track) {
receiver.stop();
}
}
} else {
// Stop senders
for (const sender of this.pc.getSenders()) {
if (!videoSourceIsMediaStreamTrack) {
if (!!sender.track) {
sender.track.stop();
}
}
this.pc.removeTrack(sender);
}
// Stop receivers
for (const receiver of this.pc.getReceivers()) {
if (!!receiver.track) {
receiver.track.stop();
}
}
}
this.pc.close();
}
} catch (err) {
console.warn('Exception disposing webrtc peer ' + err);
}
}
/**
* Function that creates an offer, sets it as local description and returns the offer param
* to send to OpenVidu Server (will be the remote description of other peer)
*/
generateOffer(): Promise<string> {
return new Promise((resolve, reject) => {
let offerAudio, offerVideo = true;
// Constraints must have both blocks
if (!!this.configuration.mediaConstraints) {
offerAudio = (typeof this.configuration.mediaConstraints.audio === 'boolean') ?
this.configuration.mediaConstraints.audio : true;
offerVideo = (typeof this.configuration.mediaConstraints.video === 'boolean') ?
this.configuration.mediaConstraints.video : true;
}
const constraints: RTCOfferOptions = {
offerToReceiveAudio: (this.configuration.mode !== 'sendonly' && offerAudio),
offerToReceiveVideo: (this.configuration.mode !== 'sendonly' && offerVideo)
};
console.debug('RTCPeerConnection constraints: ' + JSON.stringify(constraints));
if (platform.name === 'Safari' && platform.ua!!.indexOf('Safari') !== -1) {
// Safari (excluding Ionic), at least on iOS just seems to support unified plan, whereas in other browsers is not yet ready and considered experimental
if (offerAudio) {
this.pc.addTransceiver('audio', {
direction: this.configuration.mode,
});
}
if (offerVideo) {
this.pc.addTransceiver('video', {
direction: this.configuration.mode,
});
}
this.pc
.createOffer()
.then(offer => {
console.debug('Created SDP offer');
return this.pc.setLocalDescription(offer);
})
.then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
console.debug('Local description set', localDescription.sdp);
resolve(localDescription.sdp);
} else {
reject('Local description is not defined');
}
})
.catch(error => reject(error));
} else {
// Rest of platforms
this.pc.createOffer(constraints).then(offer => {
console.debug('Created SDP offer');
return this.pc.setLocalDescription(offer);
})
.then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
console.debug('Local description set', localDescription.sdp);
resolve(localDescription.sdp);
} else {
reject('Local description is not defined');
}
})
.catch(error => reject(error));
}
});
}
/**
* Function invoked when a SDP answer is received. Final step in SDP negotiation, the peer
* just needs to set the answer as its remote description
*/
processAnswer(sdpAnswer: string, needsTimeoutOnProcessAnswer: boolean): Promise<string> {
return new Promise((resolve, reject) => {
const answer: RTCSessionDescriptionInit = {
type: 'answer',
sdp: sdpAnswer
};
console.debug('SDP answer received, setting remote description');
if (this.pc.signalingState === 'closed') {
reject('RTCPeerConnection is closed');
}
if (platform['isIonicIos']) {
// Ionic iOS platform
if (needsTimeoutOnProcessAnswer) {
// 400 ms have not elapsed yet since first remote stream triggered Stream#initWebRtcPeerReceive
setTimeout(() => {
console.info('setRemoteDescription run after timeout for Ionic iOS device');
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}, 250);
} else {
// 400 ms have elapsed
this.pc.setRemoteDescription(new RTCSessionDescription(answer)).then(() => resolve()).catch(error => reject(error));
}
} else {
// Rest of platforms
this.pc.setRemoteDescription(answer).then(() => resolve()).catch(error => reject(error));
}
});
}
/**
* Callback function invoked when an ICE candidate is received
*/
addIceCandidate(iceCandidate: RTCIceCandidate): Promise<void> {
return new Promise((resolve, reject) => {
console.debug('Remote ICE candidate received', iceCandidate);
this.remoteCandidatesQueue.push(iceCandidate);
switch (this.pc.signalingState) {
case 'closed':
reject(new Error('PeerConnection object is closed'));
break;
case 'stable':
if (!!this.pc.remoteDescription) {
this.pc.addIceCandidate(iceCandidate).then(() => resolve()).catch(error => reject(error));
} else {
this.iceCandidateList.push(iceCandidate);
resolve();
}
break;
default:
this.iceCandidateList.push(iceCandidate);
resolve();
}
});
}
}
export class WebRtcPeerRecvonly extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'recvonly';
super(configuration);
}
}
export class WebRtcPeerSendonly extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'sendonly';
super(configuration);
}
}
export class WebRtcPeerSendrecv extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'sendrecv';
super(configuration);
}
}

View File

@ -0,0 +1,412 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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.
*
*/
// tslint:disable:no-string-literal
import { Stream } from '../../OpenVidu/Stream';
import platform = require('platform');
export class WebRtcStats {
private webRtcStatsEnabled = false;
private webRtcStatsIntervalId: NodeJS.Timer;
private statsInterval = 1;
private stats: any = {
inbound: {
audio: {
bytesReceived: 0,
packetsReceived: 0,
packetsLost: 0
},
video: {
bytesReceived: 0,
packetsReceived: 0,
packetsLost: 0,
framesDecoded: 0,
nackCount: 0
}
},
outbound: {
audio: {
bytesSent: 0,
packetsSent: 0,
},
video: {
bytesSent: 0,
packetsSent: 0,
framesEncoded: 0,
nackCount: 0
}
}
};
constructor(private stream: Stream) { }
public isEnabled(): boolean {
return this.webRtcStatsEnabled;
}
public initWebRtcStats(): void {
const elastestInstrumentation = localStorage.getItem('elastest-instrumentation');
if (!!elastestInstrumentation) {
// ElasTest instrumentation object found in local storage
console.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
this.webRtcStatsEnabled = true;
const instrumentation = JSON.parse(elastestInstrumentation);
this.statsInterval = instrumentation.webrtc.interval; // Interval in seconds
console.warn('localStorage item: ' + JSON.stringify(instrumentation));
this.webRtcStatsIntervalId = setInterval(() => {
this.sendStatsToHttpEndpoint(instrumentation);
}, this.statsInterval * 1000);
return;
}
console.debug('WebRtc stats not enabled');
}
public stopWebRtcStats() {
if (this.webRtcStatsEnabled) {
clearInterval(this.webRtcStatsIntervalId);
console.warn('WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
}
}
public getSelectedIceCandidateInfo(): Promise<any> {
return new Promise((resolve, reject) => {
this.getStatsAgnostic(this.stream.getRTCPeerConnection(),
(stats) => {
if ((platform.name!.indexOf('Chrome') !== -1) || (platform.name!.indexOf('Opera') !== -1)) {
let localCandidateId, remoteCandidateId, googCandidatePair;
const localCandidates = {};
const remoteCandidates = {};
for (const key in stats) {
const stat = stats[key];
if (stat.type === 'localcandidate') {
localCandidates[stat.id] = stat;
} else if (stat.type === 'remotecandidate') {
remoteCandidates[stat.id] = stat;
} else if (stat.type === 'googCandidatePair' && (stat.googActiveConnection === 'true')) {
googCandidatePair = stat;
localCandidateId = stat.localCandidateId;
remoteCandidateId = stat.remoteCandidateId;
}
}
let finalLocalCandidate = localCandidates[localCandidateId];
if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
c.candidate.indexOf(finalLocalCandidate.ipAddress) >= 0 &&
c.candidate.indexOf(finalLocalCandidate.portNumber) >= 0 &&
c.candidate.indexOf(finalLocalCandidate.priority) >= 0);
});
finalLocalCandidate.raw = !!cand[0] ? cand[0].candidate : 'ERROR: Cannot find local candidate in list of sent ICE candidates';
} else {
finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
}
let finalRemoteCandidate = remoteCandidates[remoteCandidateId];
if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (!!c.candidate &&
c.candidate.indexOf(finalRemoteCandidate.ipAddress) >= 0 &&
c.candidate.indexOf(finalRemoteCandidate.portNumber) >= 0 &&
c.candidate.indexOf(finalRemoteCandidate.priority) >= 0);
});
finalRemoteCandidate.raw = !!cand[0] ? cand[0].candidate : 'ERROR: Cannot find remote candidate in list of received ICE candidates';
} else {
finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
}
resolve({
googCandidatePair,
localCandidate: finalLocalCandidate,
remoteCandidate: finalRemoteCandidate
});
} else {
reject('Selected ICE candidate info only available for Chrome');
}
},
(error) => {
reject(error);
});
});
}
private sendStatsToHttpEndpoint(instrumentation): void {
const sendPost = (json) => {
const http: XMLHttpRequest = new XMLHttpRequest();
const url: string = instrumentation.webrtc.httpEndpoint;
http.open('POST', url, true);
http.setRequestHeader('Content-type', 'application/json');
http.onreadystatechange = () => { // Call a function when the state changes.
if (http.readyState === 4 && http.status === 200) {
console.log('WebRtc stats successfully sent to ' + url + ' for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
}
};
http.send(json);
};
const f = (stats) => {
if (platform.name!.indexOf('Firefox') !== -1) {
stats.forEach((stat) => {
let json = {};
if ((stat.type === 'inbound-rtp') &&
(
// Avoid firefox empty outbound-rtp statistics
stat.nackCount !== null &&
stat.isRemote === false &&
stat.id.startsWith('inbound') &&
stat.remoteId.startsWith('inbound')
)) {
const metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;
const jit = stat.jitter * 1000;
const metrics = {
bytesReceived: (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
jitter: jit,
packetsReceived: (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
packetsLost: (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
};
const units = {
bytesReceived: 'bytes',
jitter: 'ms',
packetsReceived: 'packets',
packetsLost: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
metrics['nackCount'] = (stat.nackCount - this.stats.inbound.video.nackCount) / this.statsInterval;
units['framesDecoded'] = 'frames';
units['nackCount'] = 'packets';
this.stats.inbound.video.framesDecoded = stat.framesDecoded;
this.stats.inbound.video.nackCount = stat.nackCount;
}
this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived;
this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived;
this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
} else if ((stat.type === 'outbound-rtp') &&
(
// Avoid firefox empty inbound-rtp statistics
stat.isRemote === false &&
stat.id.toLowerCase().includes('outbound')
)) {
const metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesSent: (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
packetsSent: (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
};
const units = {
bytesSent: 'bytes',
packetsSent: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
units['framesEncoded'] = 'frames';
this.stats.outbound.video.framesEncoded = stat.framesEncoded;
}
this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
}
});
} else if ((platform.name!.indexOf('Chrome') !== -1) || (platform.name!.indexOf('Opera') !== -1)) {
for (const key of Object.keys(stats)) {
const stat = stats[key];
if (stat.type === 'ssrc') {
let json = {};
if ('bytesReceived' in stat && (
(stat.mediaType === 'audio' && 'audioOutputLevel' in stat) ||
(stat.mediaType === 'video' && 'qpSum' in stat)
)) {
// inbound-rtp
const metricId = 'webrtc_inbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesReceived: (stat.bytesReceived - this.stats.inbound[stat.mediaType].bytesReceived) / this.statsInterval,
jitter: stat.googJitterBufferMs,
packetsReceived: (stat.packetsReceived - this.stats.inbound[stat.mediaType].packetsReceived) / this.statsInterval,
packetsLost: (stat.packetsLost - this.stats.inbound[stat.mediaType].packetsLost) / this.statsInterval
};
const units = {
bytesReceived: 'bytes',
jitter: 'ms',
packetsReceived: 'packets',
packetsLost: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesDecoded'] = (stat.framesDecoded - this.stats.inbound.video.framesDecoded) / this.statsInterval;
metrics['nackCount'] = (stat.googNacksSent - this.stats.inbound.video.nackCount) / this.statsInterval;
units['framesDecoded'] = 'frames';
units['nackCount'] = 'packets';
this.stats.inbound.video.framesDecoded = stat.framesDecoded;
this.stats.inbound.video.nackCount = stat.googNacksSent;
}
this.stats.inbound[stat.mediaType].bytesReceived = stat.bytesReceived;
this.stats.inbound[stat.mediaType].packetsReceived = stat.packetsReceived;
this.stats.inbound[stat.mediaType].packetsLost = stat.packetsLost;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
} else if ('bytesSent' in stat) {
// outbound-rtp
const metricId = 'webrtc_outbound_' + stat.mediaType + '_' + stat.ssrc;
const metrics = {
bytesSent: (stat.bytesSent - this.stats.outbound[stat.mediaType].bytesSent) / this.statsInterval,
packetsSent: (stat.packetsSent - this.stats.outbound[stat.mediaType].packetsSent) / this.statsInterval
};
const units = {
bytesSent: 'bytes',
packetsSent: 'packets'
};
if (stat.mediaType === 'video') {
metrics['framesEncoded'] = (stat.framesEncoded - this.stats.outbound.video.framesEncoded) / this.statsInterval;
units['framesEncoded'] = 'frames';
this.stats.outbound.video.framesEncoded = stat.framesEncoded;
}
this.stats.outbound[stat.mediaType].bytesSent = stat.bytesSent;
this.stats.outbound[stat.mediaType].packetsSent = stat.packetsSent;
json = {
'@timestamp': new Date(stat.timestamp).toISOString(),
'exec': instrumentation.exec,
'component': instrumentation.component,
'stream': 'webRtc',
'et_type': metricId,
'stream_type': 'composed_metrics',
'units': units
};
json[metricId] = metrics;
sendPost(JSON.stringify(json));
}
}
}
}
};
this.getStatsAgnostic(this.stream.getRTCPeerConnection(), f, (error) => { console.log(error); });
}
private standardizeReport(response) {
console.log(response);
const standardReport = {};
if (platform.name!.indexOf('Firefox') !== -1) {
Object.keys(response).forEach(key => {
console.log(response[key]);
});
return response;
}
response.result().forEach(report => {
const standardStats = {
id: report.id,
timestamp: report.timestamp,
type: report.type
};
report.names().forEach((name) => {
standardStats[name] = report.stat(name);
});
standardReport[standardStats.id] = standardStats;
});
return standardReport;
}
private getStatsAgnostic(pc, successCb, failureCb) {
if (platform.name!.indexOf('Firefox') !== -1) {
// getStats takes args in different order in Chrome and Firefox
return pc.getStats(null).then(response => {
const report = this.standardizeReport(response);
successCb(report);
}).catch(failureCb);
} else if ((platform.name!.indexOf('Chrome') !== -1) || (platform.name!.indexOf('Opera') !== -1)) {
// In Chrome, the first two arguments are reversed
return pc.getStats((response) => {
const report = this.standardizeReport(response);
successCb(report);
}, null, failureCb);
}
}
}

View File

@ -0,0 +1,34 @@
export { OpenVidu } from './OpenVidu/OpenVidu';
export { Session } from './OpenVidu/Session';
export { Publisher } from './OpenVidu/Publisher';
export { Subscriber } from './OpenVidu/Subscriber';
export { StreamManager } from './OpenVidu/StreamManager';
export { Stream } from './OpenVidu/Stream';
export { Connection } from './OpenVidu/Connection';
export { LocalRecorder } from './OpenVidu/LocalRecorder';
export { Filter } from './OpenVidu/Filter';
export { LocalRecorderState } from './OpenViduInternal/Enums/LocalRecorderState';
export { OpenViduError } from './OpenViduInternal/Enums/OpenViduError';
export { VideoInsertMode } from './OpenViduInternal/Enums/VideoInsertMode';
export { Event } from './OpenViduInternal/Events/Event';
export { ConnectionEvent } from './OpenViduInternal/Events/ConnectionEvent';
export { PublisherSpeakingEvent } from './OpenViduInternal/Events/PublisherSpeakingEvent';
export { RecordingEvent } from './OpenViduInternal/Events/RecordingEvent';
export { SessionDisconnectedEvent } from './OpenViduInternal/Events/SessionDisconnectedEvent';
export { SignalEvent } from './OpenViduInternal/Events/SignalEvent';
export { StreamEvent } from './OpenViduInternal/Events/StreamEvent';
export { StreamManagerEvent } from './OpenViduInternal/Events/StreamManagerEvent';
export { VideoElementEvent } from './OpenViduInternal/Events/VideoElementEvent';
export { StreamPropertyChangedEvent } from './OpenViduInternal/Events/StreamPropertyChangedEvent';
export { FilterEvent } from './OpenViduInternal/Events/FilterEvent';
export { Capabilities } from './OpenViduInternal/Interfaces/Public/Capabilities';
export { Device } from './OpenViduInternal/Interfaces/Public/Device';
export { EventDispatcher } from './OpenViduInternal/Interfaces/Public/EventDispatcher';
export { OpenViduAdvancedConfiguration } from './OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
export { PublisherProperties } from './OpenViduInternal/Interfaces/Public/PublisherProperties';
export { SignalOptions } from './OpenViduInternal/Interfaces/Public/SignalOptions';
export { StreamManagerVideo } from './OpenViduInternal/Interfaces/Public/StreamManagerVideo';
export { SubscriberProperties } from './OpenViduInternal/Interfaces/Public/SubscriberProperties';

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,36 @@
{
//"allowUnusedLabels": true,
"allowUnreachableCode": false,
"buildOnSave": false,
"compileOnSave": true,
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"emitBOM": false,
"forceConsistentCasingInFileNames": true,
"lib": [
"dom",
"es2015.promise",
"es5",
"scripthost"
],
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
//"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"outDir": "../../lib",
"preserveConstEnums": true,
"removeComments": true,
"rootDir": "./src",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5"
}
}

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

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

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

@ -0,0 +1,13 @@
[![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)](https://openvidu.io/docs/home/)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://groups.google.com/forum/#!forum/openvidu)
[![][OpenViduLogo]](https://openvidu.io)
openvidu-client
===
Internal Java client used by [openvidu-server](https://github.com/OpenVidu/openvidu/tree/master/openvidu-server). Can be used to implement an Android client.
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120

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

@ -0,0 +1,106 @@
<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-parent</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>openvidu-client</artifactId>
<version>1.1.0</version>
<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>https://openvidu.io</organizationUrl>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client</artifactId>
<version>${version.kurento}</version>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client-jetty</artifactId>
<version>${version.kurento}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${version.mockito.core}</version>
<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,217 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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_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.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)
throws IOException {
JsonObject params = new JsonObject();
params.addProperty(JOINROOM_ROOM_PARAM, roomName);
params.addProperty(JOINROOM_USER_PARAM, userName);
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_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,82 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 org.kurento.jsonrpc.JsonRpcErrorException;
public class OpenViduException extends JsonRpcErrorException {
private static final long serialVersionUID = 1L;
public static enum Code {
GENERIC_ERROR_CODE(999), WRONG_PATH_CODE(998),
TRANSPORT_ERROR_CODE(803), TRANSPORT_RESPONSE_ERROR_CODE(802), TRANSPORT_REQUEST_ERROR_CODE(801),
MEDIA_TYPE_STREAM_INCOMPATIBLE_WITH_RECORDING_PROPERTIES_ERROR_CODE(309),
MEDIA_TYPE_RECORDING_PROPERTIES_ERROR_CODE(308), 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(10),
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), EXISTING_FILTER_ALREADY_APPLIED_ERROR_CODE(405),
FILTER_NOT_APPLIED_ERROR_CODE(406), FILTER_EVENT_LISTENER_NOT_FOUND(407),
USER_METADATA_FORMAT_INVALID_ERROR_CODE(500),
SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601),
SIGNAL_MESSAGE_INVALID_ERROR_CODE(602),
DOCKER_NOT_FOUND(709), RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707),
RECORDING_DELETE_ERROR_CODE(706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704),
RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701);
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(code.getValue(), message);
this.code = code;
}
public int getCodeValue() {
return code.getValue();
}
@Override
public String toString() {
return super.toString();
}
}

View File

@ -0,0 +1,222 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 data = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_DATA_PARAM, String.class);
String from = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_FROM_PARAM, String.class);
String type = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_TYPE_PARAM, String.class);
SendMessageInfo eventInfo = new SendMessageInfo(data, from, type);
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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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,183 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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_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_SECRET_PARAM = "secret";
public static final String JOINROOM_PLATFORM_PARAM = "platform";
public static final String JOINROOM_RECORDER_PARAM = "recorder";
public static final String JOINROOM_PEERID_PARAM = "id";
public static final String JOINROOM_PEERCREATEDAT_PARAM = "createdAt";
public static final String JOINROOM_PEERSTREAMS_PARAM = "streams";
public static final String JOINROOM_PEERSTREAMID_PARAM = "id";
public static final String JOINROOM_PEERSTREAMHASAUDIO_PARAM = "hasAudio";
public static final String JOINROOM_PEERSTREAMHASVIDEO_PARAM = "hasVideo";
public static final String JOINROOM_PEERSTREAMAUDIOACTIVE_PARAM = "audioActive";
public static final String JOINROOM_PEERSTREAMVIDEOACTIVE_PARAM = "videoActive";
public static final String JOINROOM_PEERSTREAMTYPEOFVIDEO_PARAM = "typeOfVideo";
public static final String JOINROOM_PEERSTREAMFRAMERATE_PARAM = "frameRate";
public static final String JOINROOM_PEERSTREAMVIDEODIMENSIONS_PARAM = "videoDimensions";
public static final String JOINROOM_PEERSTREAMFILTER_PARAM = "filter";
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 PUBLISHVIDEO_STREAMID_PARAM = "id";
public static final String PUBLISHVIDEO_CREATEDAT_PARAM = "createdAt";
public static final String PUBLISHVIDEO_HASAUDIO_PARAM = "hasAudio";
public static final String PUBLISHVIDEO_HASVIDEO_PARAM = "hasVideo";
public static final String PUBLISHVIDEO_AUDIOACTIVE_PARAM = "audioActive";
public static final String PUBLISHVIDEO_VIDEOACTIVE_PARAM = "videoActive";
public static final String PUBLISHVIDEO_TYPEOFVIDEO_PARAM = "typeOfVideo";
public static final String PUBLISHVIDEO_FRAMERATE_PARAM = "frameRate";
public static final String PUBLISHVIDEO_VIDEODIMENSIONS_PARAM = "videoDimensions";
public static final String PUBLISHVIDEO_KURENTOFILTER_PARAM = "filter";
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";
public static final String STREAMPROPERTYCHANGED_METHOD = "streamPropertyChanged";
public static final String STREAMPROPERTYCHANGED_CONNECTIONID_PARAM = "connectionId";
public static final String STREAMPROPERTYCHANGED_STREAMID_PARAM = "streamId";
public static final String STREAMPROPERTYCHANGED_PROPERTY_PARAM = "property";
public static final String STREAMPROPERTYCHANGED_NEWVALUE_PARAM = "newValue";
public static final String STREAMPROPERTYCHANGED_REASON_PARAM = "reason";
public static final String FORCEDISCONNECT_METHOD = "forceDisconnect";
public static final String FORCEDISCONNECT_CONNECTIONID_PARAM = "connectionId";
public static final String FORCEUNPUBLISH_METHOD = "forceUnpublish";
public static final String FORCEUNPUBLISH_STREAMID_PARAM = "streamId";
public static final String APPLYFILTER_METHOD = "applyFilter";
public static final String FILTER_STREAMID_PARAM = "streamId";
public static final String FILTER_TYPE_PARAM = "type";
public static final String FILTER_OPTIONS_PARAM = "options";
public static final String FILTER_METHOD_PARAM = "method";
public static final String FILTER_PARAMS_PARAM = "params";
public static final String EXECFILTERMETHOD_METHOD = "execFilterMethod";
public static final String EXECFILTERMETHOD_LASTEXECMETHOD_PARAM = "lastExecMethod";
public static final String REMOVEFILTER_METHOD = "removeFilter";
public static final String ADDFILTEREVENTLISTENER_METHOD = "addFilterEventListener";
public static final String REMOVEFILTEREVENTLISTENER_METHOD = "removeFilterEventListener";
public static final String FILTEREVENTDISPATCHED_METHOD = "filterEventDispatched";
public static final String FILTEREVENTLISTENER_CONNECTIONID_PARAM = "connectionId";
public static final String FILTEREVENTLISTENER_STREAMID_PARAM = "streamId";
public static final String FILTEREVENTLISTENER_FILTERTYPE_PARAM = "filterType";
public static final String FILTEREVENTLISTENER_EVENTTYPE_PARAM = "eventType";
public static final String FILTEREVENTLISTENER_DATA_PARAM = "data";
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
public static final String PARTICIPANTJOINED_METHOD = "participantJoined";
public static final String PARTICIPANTJOINED_USER_PARAM = "id";
public static final String PARTICIPANTJOINED_CREATEDAT_PARAM = "createdAt";
public static final String PARTICIPANTJOINED_METADATA_PARAM = "metadata";
public static final String PARTICIPANTLEFT_METHOD = "participantLeft";
public static final String PARTICIPANTLEFT_NAME_PARAM = "connectionId";
public static final String PARTICIPANTLEFT_REASON_PARAM = "reason";
public static final String PARTICIPANTEVICTED_METHOD = "participantEvicted";
public static final String PARTICIPANTEVICTED_CONNECTIONID_PARAM = "connectionId";
public static final String PARTICIPANTEVICTED_REASON_PARAM = "reason";
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 PARTICIPANTPUBLISHED_CREATEDAT_PARAM = "createdAt";
public static final String PARTICIPANTPUBLISHED_HASAUDIO_PARAM = "hasAudio";
public static final String PARTICIPANTPUBLISHED_HASVIDEO_PARAM = "hasVideo";
public static final String PARTICIPANTPUBLISHED_AUDIOACTIVE_PARAM = "audioActive";
public static final String PARTICIPANTPUBLISHED_VIDEOACTIVE_PARAM = "videoActive";
public static final String PARTICIPANTPUBLISHED_TYPEOFVIDEO_PARAM = "typeOfVideo";
public static final String PARTICIPANTPUBLISHED_FRAMERATE_PARAM = "frameRate";
public static final String PARTICIPANTPUBLISHED_VIDEODIMENSIONS_PARAM = "videoDimensions";
public static final String PARTICIPANTPUBLISHED_FILTER_PARAM = "filter";
public static final String PARTICIPANTUNPUBLISHED_METHOD = "participantUnpublished";
public static final String PARTICIPANTUNPUBLISHED_NAME_PARAM = "connectionId";
public static final String PARTICIPANTUNPUBLISHED_REASON_PARAM = "reason";
public static final String PARTICIPANTSENDMESSAGE_METHOD = "sendMessage";
public static final String PARTICIPANTSENDMESSAGE_DATA_PARAM = "data";
public static final String PARTICIPANTSENDMESSAGE_FROM_PARAM = "from";
public static final String PARTICIPANTSENDMESSAGE_TYPE_PARAM = "type";
public static final String ROOMCLOSED_METHOD = "roomClosed";
public static final String ROOMCLOSED_ROOM_PARAM = "sessionId";
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_SENDERCONNECTIONID_PARAM = "senderConnectionId";
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 RECORDINGSTARTED_METHOD = "recordingStarted";
public static final String RECORDINGSTARTED_ID_PARAM = "id";
public static final String RECORDINGSTARTED_NAME_PARAM = "name";
public static final String RECORDINGSTOPPED_REASON_PARAM = "reason";
public static final String RECORDINGSTOPPED_METHOD = "recordingStopped";
public static final String RECORDINGSTOPPED_ID_PARAM = "id";
public static final String CUSTOM_NOTIFICATION = "custonNotification";
public static final String RECORDER_PARTICIPANT_PUBLICID = "RECORDER";
}

View File

@ -0,0 +1,54 @@
/*
* (C) Copyright 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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 2017-2019 OpenVidu (https://openvidu.io/)
*
* 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"), 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();
});
});

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