mirror of https://github.com/OpenVidu/openvidu.git
Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
3634a5c8e3 |
|
@ -1,445 +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
|
|
@ -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
|
||||
|
|
@ -25,5 +25,3 @@ nbactions.xml
|
|||
*/.classpath
|
||||
*/.settings/*
|
||||
*/.tscache/*
|
||||
|
||||
.factorypath
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "openvidu-livekit"]
|
||||
path = openvidu-livekit
|
||||
url = https://github.com/OpenVidu/openvidu-livekit.git
|
2
NOTICE
2
NOTICE
|
@ -1,4 +1,4 @@
|
|||
(C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
|
||||
(C) Copyright 2017-2020 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.
|
||||
|
|
11
README.md
11
README.md
|
@ -1,11 +1,9 @@
|
|||
[](#backers) [](#sponsors)
|
||||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml)
|
||||
[](#backers) [](#sponsors) [](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://npmjs.org/package/openvidu-browser)
|
||||
[](https://npmjs.org/package/openvidu-browser)
|
||||
|
||||
|
||||
[](https://docs.openvidu.io/en/stable/?badge=stable)
|
||||
[](https://docs.openvidu.io/en/stable/?badge=stable)
|
||||
[](https://hub.docker.com/r/openvidu/openvidu-server-kms)
|
||||
[](https://openvidu.discourse.group/)
|
||||
[](https://twitter.com/openvidu)
|
||||
|
@ -35,11 +33,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>
|
||||
|
||||
## 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
|
||||
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
# Next release
|
||||
|
||||
* IP Cameras support
|
||||
* Angular version update in library and tutorials
|
||||
* Ionic version update in tutorials
|
||||
* Dynamic change of cluster size (PRO)
|
||||
* On premises installation license management (PRO)
|
||||
|
||||
# Medium term
|
||||
* Multiple streams per participant
|
||||
* OpenVidu Call features for One to Many sessions
|
||||
* Revamped documentation and tutorials
|
||||
* Out of the box deployment in Azure, GCP and DO cloud providers
|
||||
* Elasticity (auto-scaling) (PRO)
|
||||
* Kubernetes support (PRO)
|
||||
* Simulcast and SVC (PRO)
|
||||
* Improved resource consumption (PRO)
|
||||
|
||||
# Long term
|
||||
* Dynamic change of participant roles
|
||||
* Granular permissions for participants
|
||||
* P2P sessions with 2 participants (PRO)
|
||||
* KurentoClient for advanced management of media sessions (PRO)
|
||||
* RTMP, HLS and DASH support (PRO)
|
||||
* Sessions migration between cluster nodes (for reduced resource consumption) (PRO)
|
|
@ -0,0 +1,62 @@
|
|||
# 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/
|
||||
static/*.js
|
|
@ -0,0 +1 @@
|
|||
docs/
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
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/**",
|
||||
"**/OpenViduInternal/Logger/**",
|
||||
],
|
||||
excludeExternals: true,
|
||||
excludePrivate: true,
|
||||
excludeProtected: true,
|
||||
excludeNotExported: true,
|
||||
theme: "default",
|
||||
readme: "none",
|
||||
includeVersion: true,
|
||||
listInvalidSymbolLinks: true,
|
||||
out: "./docs"
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
#!/bin/bash
|
||||
|
||||
if [[ -z "$BASEHREF_VERSION" ]]; then
|
||||
echo "Example of use: \"BASEHREF_VERSION=2.12.0 ${0}\"" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Replace version from "stable" to the specified one in all TypeDoc links
|
||||
grep -rl '/en/stable/' src | xargs sed -i -e 's|/en/stable/|/en/'${BASEHREF_VERSION}'/|g'
|
||||
|
||||
# Generate TypeDoc
|
||||
./node_modules/typedoc/bin/typedoc --options ./config/typedoc.js ./src
|
||||
|
||||
# Return links to "stable" version
|
||||
grep -rl '/en/'${BASEHREF_VERSION}'/' src | xargs sed -i -e 's|/en/'${BASEHREF_VERSION}'/|/en/stable/|g'
|
||||
|
||||
# Clean previous docs from openvidu.io-docs repo and copy new ones
|
||||
rm -rf ../../openvidu.io-docs/docs/api/openvidu-browser/*
|
||||
cp -R ./docs/. ../../openvidu.io-docs/docs/api/openvidu-browser
|
|
@ -0,0 +1,46 @@
|
|||
{
|
||||
"author": "OpenVidu",
|
||||
"dependencies": {
|
||||
"freeice": "2.2.2",
|
||||
"hark": "1.2.3",
|
||||
"jsnlog": "2.30.0",
|
||||
"platform": "1.3.6",
|
||||
"uuid": "8.3.2",
|
||||
"wolfy87-eventemitter": "5.2.9"
|
||||
},
|
||||
"description": "OpenVidu Browser",
|
||||
"devDependencies": {
|
||||
"@types/node": "15.12.2",
|
||||
"@types/platform": "1.3.3",
|
||||
"browserify": "17.0.0",
|
||||
"grunt": "1.4.1",
|
||||
"grunt-cli": "1.4.3",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-sass": "2.0.0",
|
||||
"grunt-contrib-uglify": "5.0.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"grunt-postcss": "0.9.0",
|
||||
"grunt-string-replace": "1.3.1",
|
||||
"grunt-ts": "6.0.0-beta.22",
|
||||
"terser": "5.7.0",
|
||||
"tsify": "5.0.4",
|
||||
"tslint": "6.1.3",
|
||||
"typedoc": "0.19.2",
|
||||
"typescript": "4.0.7"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
"name": "openvidu-browser",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/OpenVidu/openvidu"
|
||||
},
|
||||
"scripts": {
|
||||
"browserify": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && 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:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/terser/bin/terser --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 --target es5 --lib dom,es5,es2015.promise,scripthost",
|
||||
"docs": "./generate-docs.sh"
|
||||
},
|
||||
"types": "lib/index.d.ts",
|
||||
"version": "2.20.0"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { OpenVidu } from './OpenVidu/OpenVidu';
|
||||
import { JL } from 'jsnlog';
|
||||
|
||||
if (window) {
|
||||
window['OpenVidu'] = OpenVidu;
|
||||
}
|
||||
|
||||
// Disable jsnlog when library is loaded
|
||||
JL.setOptions({ enabled: false })
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
|
||||
import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
|
||||
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
|
||||
import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Role of the connection.
|
||||
* - `SUBSCRIBER`: can subscribe to published Streams of other users by calling [[Session.subscribe]]
|
||||
* - `PUBLISHER`: SUBSCRIBER permissions + can publish their own Streams by calling [[Session.publish]]
|
||||
* - `MODERATOR`: SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection by call [[Session.forceUnpublish]] and [[Session.forceDisconnect]]
|
||||
*
|
||||
* **Only defined for the local connection. In remote connections will be `undefined`**
|
||||
*/
|
||||
role: string;
|
||||
|
||||
/**
|
||||
* Whether the streams published by this connection will be recorded or not. This only affects [INDIVIDUAL recording](/en/stable/advanced-features/recording#selecting-streams-to-be-recorded) <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a>
|
||||
*
|
||||
* **Only defined for the local connection. In remote connections will be `undefined`**
|
||||
*/
|
||||
record: boolean;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
stream?: Stream;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
localOptions: LocalConnectionOptions | undefined;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
remoteOptions: RemoteConnectionOptions | undefined;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
disposed = false;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
rpcSessionId: string;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(private session: Session, connectionOptions: LocalConnectionOptions | RemoteConnectionOptions) {
|
||||
let msg = "'Connection' created ";
|
||||
if (!!(<LocalConnectionOptions>connectionOptions).role) {
|
||||
// Connection is local
|
||||
this.localOptions = <LocalConnectionOptions>connectionOptions;
|
||||
this.connectionId = this.localOptions.id;
|
||||
this.creationTime = this.localOptions.createdAt;
|
||||
this.data = this.localOptions.metadata;
|
||||
this.rpcSessionId = this.localOptions.sessionId;
|
||||
this.role = this.localOptions.role;
|
||||
this.record = this.localOptions.record;
|
||||
msg += '(local)';
|
||||
} else {
|
||||
// Connection is remote
|
||||
this.remoteOptions = <RemoteConnectionOptions>connectionOptions;
|
||||
this.connectionId = this.remoteOptions.id;
|
||||
this.creationTime = this.remoteOptions.createdAt;
|
||||
if (this.remoteOptions.metadata) {
|
||||
this.data = this.remoteOptions.metadata;
|
||||
}
|
||||
if (this.remoteOptions.streams) {
|
||||
this.initRemoteStreams(this.remoteOptions.streams);
|
||||
}
|
||||
msg += "(remote) with 'connectionId' [" + this.remoteOptions.id + ']';
|
||||
}
|
||||
logger.info(msg);
|
||||
}
|
||||
|
||||
|
||||
/* Hidden methods */
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
sendIceCandidate(candidate: RTCIceCandidate): void {
|
||||
|
||||
logger.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) {
|
||||
logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
|
||||
this.session.emitEvent('exception', [new ExceptionEvent(this.session, ExceptionEventName.ICE_CANDIDATE_ERROR, this.session, "There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side", 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);
|
||||
});
|
||||
|
||||
logger.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;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 '../OpenViduInternal/Events/Event';
|
||||
import EventEmitter = require('wolfy87-eventemitter');
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
export abstract class EventDispatcher {
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
userHandlerArrowHandler: WeakMap<(event: Event) => void, (event: Event) => void> = new WeakMap();
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
ee = new EventEmitter();
|
||||
|
||||
/**
|
||||
* Adds function `handler` to handle event `type`
|
||||
*
|
||||
* @returns The EventDispatcher object
|
||||
*/
|
||||
abstract 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
|
||||
*/
|
||||
abstract once(type: string, handler: (event: Event) => void): EventDispatcher;
|
||||
|
||||
/**
|
||||
* 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): EventDispatcher {
|
||||
if (!handler) {
|
||||
this.ee.removeAllListeners(type);
|
||||
} else {
|
||||
// Must remove internal arrow function handler paired with user handler
|
||||
const arrowHandler = this.userHandlerArrowHandler.get(handler);
|
||||
if (!!arrowHandler) {
|
||||
this.ee.off(type, arrowHandler);
|
||||
}
|
||||
this.userHandlerArrowHandler.delete(handler);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
||||
const arrowHandler = event => {
|
||||
if (event) {
|
||||
logger.info(message, event);
|
||||
} else {
|
||||
logger.info(message);
|
||||
}
|
||||
handler(event);
|
||||
};
|
||||
this.userHandlerArrowHandler.set(handler, arrowHandler);
|
||||
this.ee.on(type, arrowHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
||||
const arrowHandler = event => {
|
||||
if (event) {
|
||||
logger.info(message, event);
|
||||
} else {
|
||||
logger.info(message);
|
||||
}
|
||||
handler(event);
|
||||
// Remove handler from map after first and only execution
|
||||
this.userHandlerArrowHandler.delete(handler);
|
||||
};
|
||||
this.userHandlerArrowHandler.set(handler, arrowHandler);
|
||||
this.ee.once(type, arrowHandler);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* **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: Map<string, (event: FilterEvent) => void> = new Map();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
stream: Stream;
|
||||
private logger: OpenViduLogger;
|
||||
|
||||
|
||||
/**
|
||||
* @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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.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";
|
||||
logger.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) {
|
||||
logger.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 {
|
||||
logger.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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.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) {
|
||||
logger.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.set(eventType, handler);
|
||||
logger.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<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.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) {
|
||||
logger.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 {
|
||||
this.handlers.delete(eventType);
|
||||
logger.info('Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId);
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,387 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
declare var MediaRecorder: any;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
platform = PlatformUtils.getInstance();
|
||||
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`
|
||||
*
|
||||
* @param mimeType The [MediaRecorder.mimeType](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/mimeType) to be used to record this Stream.
|
||||
* Make sure the platform supports it or the promise will return an error. If this parameter is not provided, the MediaRecorder will use the default codecs available in the platform
|
||||
*
|
||||
* @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(mimeType?: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (typeof MediaRecorder === 'undefined') {
|
||||
logger.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'));
|
||||
}
|
||||
logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
|
||||
|
||||
let options = {};
|
||||
if (typeof MediaRecorder.isTypeSupported === 'function') {
|
||||
if (!!mimeType) {
|
||||
if (!MediaRecorder.isTypeSupported(mimeType)) {
|
||||
reject(new Error('mimeType "' + mimeType + '" is not supported'));
|
||||
}
|
||||
options = { mimeType };
|
||||
} else {
|
||||
logger.log('No mimeType parameter provided. Using default codecs');
|
||||
}
|
||||
} else {
|
||||
logger.warn('MediaRecorder#isTypeSupported is not supported. Using default codecs');
|
||||
}
|
||||
|
||||
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
|
||||
this.mediaRecorder.start(10);
|
||||
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
this.mediaRecorder.ondataavailable = (e) => {
|
||||
this.chunks.push(e.data);
|
||||
};
|
||||
|
||||
this.mediaRecorder.onerror = (e) => {
|
||||
logger.error('MediaRecorder error: ', e);
|
||||
};
|
||||
|
||||
this.mediaRecorder.onstart = () => {
|
||||
logger.log('MediaRecorder started (state=' + this.mediaRecorder.state + ')');
|
||||
};
|
||||
|
||||
this.mediaRecorder.onstop = () => {
|
||||
this.onStopDefault();
|
||||
};
|
||||
|
||||
this.mediaRecorder.onpause = () => {
|
||||
logger.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ')');
|
||||
};
|
||||
|
||||
this.mediaRecorder.onresume = () => {
|
||||
logger.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ')');
|
||||
};
|
||||
|
||||
this.mediaRecorder.onwarning = (e) => {
|
||||
logger.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<void> {
|
||||
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<void> {
|
||||
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;
|
||||
resolve();
|
||||
} 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<void> {
|
||||
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;
|
||||
resolve();
|
||||
} 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.isSafariBrowser()) {
|
||||
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 {
|
||||
logger.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;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,733 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 './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 { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
/**
|
||||
* Packs local media streams. Participants can publish it to a session. Initialized with [[OpenVidu.initPublisher]] method
|
||||
*
|
||||
* ### Available event listeners (and events dispatched)
|
||||
*
|
||||
* - accessAllowed
|
||||
* - accessDenied
|
||||
* - accessDialogOpened
|
||||
* - accessDialogClosed
|
||||
* - streamCreated ([[StreamEvent]])
|
||||
* - streamDestroyed ([[StreamEvent]])
|
||||
* - _All events inherited from [[StreamManager]] class_
|
||||
*/
|
||||
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;
|
||||
protected properties: PublisherProperties;
|
||||
private permissionDialogTimeout: NodeJS.Timer;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
openvidu: OpenVidu;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
videoReference: HTMLVideoElement;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
screenShareResizeInterval: NodeJS.Timer;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(targEl: string | HTMLElement, properties: PublisherProperties, openvidu: OpenVidu) {
|
||||
super(new Stream((!!openvidu.session) ? openvidu.session : new Session(openvidu), { publisherProperties: properties, mediaConstraints: {} }), targEl);
|
||||
platform = PlatformUtils.getInstance();
|
||||
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) {
|
||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
||||
affectedMediaStream.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) {
|
||||
logger.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.session.sendVideoData(this.stream.streamManager);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.stream.audioActive = value;
|
||||
logger.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) {
|
||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
||||
affectedMediaStream.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) {
|
||||
logger.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.session.sendVideoData(this.stream.streamManager);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.stream.videoActive = value;
|
||||
logger.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
|
||||
* without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
|
||||
* and publishing the new one).
|
||||
*
|
||||
* You can get this new MediaStreamTrack by using the native Web API or simply with [[OpenVidu.getUserMedia]] method.
|
||||
*
|
||||
* **WARNING: this method has been proven to work in the majority of cases, but there may be some combinations of published/replaced tracks that may be incompatible
|
||||
* between them and break the connection in OpenVidu Server. A complete renegotiation may be the only solution in this case.
|
||||
* Visit [RTCRtpSender.replaceTrack](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack) documentation for further details.**
|
||||
*
|
||||
* @param track The [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one.
|
||||
* If it is an audio track, the current audio track will be the replaced one. If it is a video track, the current video track will be the replaced one.
|
||||
*
|
||||
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case
|
||||
*/
|
||||
async replaceTrack(track: MediaStreamTrack): Promise<void> {
|
||||
|
||||
const replaceTrackInMediaStream = (): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const mediaStream: MediaStream = this.stream.displayMyRemote() ? this.stream.localMediaStreamWhenSubscribedToRemote! : this.stream.getMediaStream();
|
||||
let removedTrack: MediaStreamTrack;
|
||||
if (track.kind === 'video') {
|
||||
removedTrack = mediaStream.getVideoTracks()[0];
|
||||
} else {
|
||||
removedTrack = mediaStream.getAudioTracks()[0];
|
||||
}
|
||||
mediaStream.removeTrack(removedTrack);
|
||||
removedTrack.stop();
|
||||
mediaStream.addTrack(track);
|
||||
if (track.kind === 'video' && this.stream.isLocalStreamPublished) {
|
||||
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
|
||||
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
|
||||
const replaceTrackInRtcRtpSender = (): Promise<void> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
|
||||
let sender: RTCRtpSender | undefined;
|
||||
if (track.kind === 'video') {
|
||||
sender = senders.find(s => !!s.track && s.track.kind === 'video');
|
||||
if (!sender) {
|
||||
reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
||||
return;
|
||||
}
|
||||
} else if (track.kind === 'audio') {
|
||||
sender = senders.find(s => !!s.track && s.track.kind === 'audio');
|
||||
if (!sender) {
|
||||
reject(new Error('There\'s no replaceable track for that kind of MediaStreamTrack in this Publisher object'));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
reject(new Error('Unknown track kind ' + track.kind));
|
||||
return;
|
||||
}
|
||||
(sender as RTCRtpSender).replaceTrack(track).then(() => {
|
||||
resolve();
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Set field "enabled" of the new track to the previous value
|
||||
const trackOriginalEnabledValue: boolean = track.enabled;
|
||||
if (track.kind === 'video') {
|
||||
track.enabled = this.stream.videoActive;
|
||||
} else if (track.kind === 'audio') {
|
||||
track.enabled = this.stream.audioActive;
|
||||
}
|
||||
try {
|
||||
if (this.stream.isLocalStreamPublished) {
|
||||
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
|
||||
// If it has not been published yet, replacing it on the MediaStream object is enough
|
||||
await replaceTrackInRtcRtpSender();
|
||||
return await replaceTrackInMediaStream();
|
||||
} else {
|
||||
// Publisher not published. Simply replace the track on the local MediaStream
|
||||
return await replaceTrackInMediaStream();
|
||||
}
|
||||
} catch (error) {
|
||||
track.enabled = trackOriginalEnabledValue;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hidden methods */
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initialize(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
let constraints: MediaStreamConstraints = {};
|
||||
let constraintsAux: MediaStreamConstraints = {};
|
||||
const timeForDialogEvent = 1500;
|
||||
let startTime;
|
||||
|
||||
const errorCallback = (openViduError: OpenViduError) => {
|
||||
this.accessDenied = true;
|
||||
this.accessAllowed = false;
|
||||
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`)
|
||||
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.initializeVideoReference(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()) {
|
||||
// Has video track
|
||||
this.getVideoDimensions(mediaStream).then(dimensions => {
|
||||
this.stream.videoDimensions = {
|
||||
width: dimensions.width,
|
||||
height: dimensions.height
|
||||
};
|
||||
|
||||
if (this.stream.isSendScreen()) {
|
||||
// Set interval to listen for screen resize events
|
||||
this.screenShareResizeInterval = setInterval(() => {
|
||||
const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
|
||||
const newWidth = settings.width;
|
||||
const newHeight = settings.height;
|
||||
if (this.stream.isLocalStreamPublished &&
|
||||
(newWidth !== this.stream.videoDimensions.width || newHeight !== this.stream.videoDimensions.height)) {
|
||||
this.openvidu.sendVideoDimensionsChangedEvent(
|
||||
this,
|
||||
'screenResized',
|
||||
this.stream.videoDimensions.width,
|
||||
this.stream.videoDimensions.height,
|
||||
newWidth || 0,
|
||||
newHeight || 0
|
||||
);
|
||||
}
|
||||
}, 650);
|
||||
}
|
||||
|
||||
this.stream.isLocalStreamReadyToPublish = true;
|
||||
this.stream.ee.emitEvent('stream-ready-to-publish', []);
|
||||
});
|
||||
} else {
|
||||
// Only audio track (no videoDimensions)
|
||||
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);
|
||||
mediaStream.getAudioTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
mediaStream.getVideoTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
errorCallback(this.openvidu.generateAudioDeviceError(error, constraints));
|
||||
return;
|
||||
});
|
||||
} else {
|
||||
successCallback(mediaStream);
|
||||
}
|
||||
};
|
||||
|
||||
const getMediaError = error => {
|
||||
logger.error(`getMediaError: ${error.toString()}`);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
this.openvidu.generateMediaConstraints(this.properties)
|
||||
.then(myConstraints => {
|
||||
|
||||
if (!!myConstraints.videoTrack && !!myConstraints.audioTrack ||
|
||||
!!myConstraints.audioTrack && myConstraints.constraints?.video === false ||
|
||||
!!myConstraints.videoTrack && myConstraints.constraints?.audio === false) {
|
||||
// No need to call getUserMedia at all. MediaStreamTracks already provided
|
||||
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream()));
|
||||
// Return as we do not need to process further
|
||||
return;
|
||||
}
|
||||
|
||||
constraints = myConstraints.constraints;
|
||||
|
||||
const outboundStreamOptions = {
|
||||
mediaConstraints: constraints,
|
||||
publisherProperties: this.properties
|
||||
};
|
||||
this.stream.setOutboundStreamOptions(outboundStreamOptions);
|
||||
|
||||
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.isElectron()) {
|
||||
navigator.mediaDevices['getDisplayMedia']({ video: true })
|
||||
.then(mediaStream => {
|
||||
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
||||
getMediaSuccess(mediaStream, definedAudioConstraint);
|
||||
})
|
||||
.catch(error => {
|
||||
getMediaError(error);
|
||||
});
|
||||
} else {
|
||||
navigator.mediaDevices.getUserMedia(constraintsAux)
|
||||
.then(mediaStream => {
|
||||
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
||||
getMediaSuccess(mediaStream, definedAudioConstraint);
|
||||
})
|
||||
.catch(error => {
|
||||
getMediaError(error);
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
.catch((error: OpenViduError) => {
|
||||
errorCallback(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*
|
||||
* To obtain the videoDimensions we wait for the video reference to have enough metadata
|
||||
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
|
||||
* use the HTMLVideoElement properties videoWidth and videoHeight
|
||||
*/
|
||||
getVideoDimensions(mediaStream: MediaStream): Promise<{ width: number, height: number }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
|
||||
const requiresDomInsertion: boolean = platform.isIonicIos() || platform.isIOSWithSafari();
|
||||
|
||||
let loadedmetadataListener;
|
||||
const resolveDimensions = () => {
|
||||
let width: number;
|
||||
let height: number;
|
||||
if (typeof this.stream.getMediaStream().getVideoTracks()[0].getSettings === 'function') {
|
||||
const settings = this.stream.getMediaStream().getVideoTracks()[0].getSettings();
|
||||
width = settings.width || this.videoReference.videoWidth;
|
||||
height = settings.height || this.videoReference.videoHeight;
|
||||
} else {
|
||||
logger.warn('MediaStreamTrack does not have getSettings method on ' + platform.getDescription());
|
||||
width = this.videoReference.videoWidth;
|
||||
height = this.videoReference.videoHeight;
|
||||
}
|
||||
|
||||
if (loadedmetadataListener != null) {
|
||||
this.videoReference.removeEventListener('loadedmetadata', loadedmetadataListener);
|
||||
}
|
||||
if (requiresDomInsertion) {
|
||||
document.body.removeChild(this.videoReference);
|
||||
}
|
||||
|
||||
resolve({ width, height });
|
||||
}
|
||||
|
||||
if (this.videoReference.readyState >= 1) {
|
||||
// The video already has metadata available
|
||||
// No need of loadedmetadata event
|
||||
resolveDimensions();
|
||||
} else {
|
||||
// The video does not have metadata available yet
|
||||
// Must listen to loadedmetadata event
|
||||
loadedmetadataListener = () => {
|
||||
if (!this.videoReference.videoWidth) {
|
||||
let interval = setInterval(() => {
|
||||
if (!!this.videoReference.videoWidth) {
|
||||
clearInterval(interval);
|
||||
resolveDimensions();
|
||||
}
|
||||
}, 40);
|
||||
} else {
|
||||
resolveDimensions();
|
||||
}
|
||||
};
|
||||
this.videoReference.addEventListener('loadedmetadata', loadedmetadataListener);
|
||||
if (requiresDomInsertion) {
|
||||
document.body.appendChild(this.videoReference);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
reestablishStreamPlayingEvent() {
|
||||
if (this.ee.getListeners('streamPlaying').length > 0) {
|
||||
this.addPlayEventToFirstVideo();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initializeVideoReference(mediaStream: MediaStream) {
|
||||
this.videoReference = document.createElement('video');
|
||||
this.videoReference.setAttribute('muted', 'true');
|
||||
this.videoReference.style.display = 'none';
|
||||
if (platform.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
/* 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
|
@ -0,0 +1,593 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { Subscriber } from './Subscriber';
|
||||
import { EventDispatcher } from './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 { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
|
||||
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* ### Available event listeners (and events dispatched)
|
||||
*
|
||||
* - videoElementCreated ([[VideoElementEvent]])
|
||||
* - videoElementDestroyed ([[VideoElementEvent]])
|
||||
* - streamPlaying ([[StreamManagerEvent]])
|
||||
* - streamPropertyChanged ([[StreamPropertyChangedEvent]])
|
||||
* - publisherStartSpeaking ([[PublisherSpeakingEvent]])
|
||||
* - publisherStopSpeaking ([[PublisherSpeakingEvent]])
|
||||
* - streamAudioVolumeChange ([[StreamManagerEvent]])
|
||||
*
|
||||
*/
|
||||
export class StreamManager extends 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
|
||||
*/
|
||||
protected firstVideoElement?: StreamManagerVideo;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
protected element: HTMLElement;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
protected canPlayListener: EventListener;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
private streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
private lazyLaunchVideoElementCreatedEvent = false;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(stream: Stream, targetElement?: HTMLElement | string) {
|
||||
super();
|
||||
platform = PlatformUtils.getInstance();
|
||||
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.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
||||
this.firstVideoElement.video.setAttribute('playsinline', 'true');
|
||||
}
|
||||
this.targetElement = targEl;
|
||||
this.element = targEl;
|
||||
}
|
||||
}
|
||||
|
||||
this.canPlayListener = () => {
|
||||
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||
if (this.remote) {
|
||||
logger.info("Remote 'Stream' with id [" + this.stream.streamId + '] video is now playing');
|
||||
this.ee.emitEvent('videoPlaying', [new VideoElementEvent(this.videos[0].video, this, 'videoPlaying')]);
|
||||
} else {
|
||||
if (!this.stream.displayMyRemote()) {
|
||||
logger.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 {
|
||||
logger.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')]);
|
||||
}
|
||||
}
|
||||
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* See [[EventDispatcher.on]]
|
||||
*/
|
||||
on(type: string, handler: (event: Event) => void): EventDispatcher {
|
||||
|
||||
super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler)
|
||||
|
||||
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 (this.stream.hasAudio) {
|
||||
if (type === 'publisherStartSpeaking') {
|
||||
this.stream.enableHarkSpeakingEvent();
|
||||
}
|
||||
if (type === 'publisherStopSpeaking') {
|
||||
this.stream.enableHarkStoppedSpeakingEvent();
|
||||
}
|
||||
if (type === 'streamAudioVolumeChange') {
|
||||
this.stream.enableHarkVolumeChangeEvent(false);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See [[EventDispatcher.once]]
|
||||
*/
|
||||
once(type: string, handler: (event: Event) => void): StreamManager {
|
||||
|
||||
super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
|
||||
|
||||
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 (this.stream.hasAudio) {
|
||||
if (type === 'publisherStartSpeaking') {
|
||||
this.stream.enableOnceHarkSpeakingEvent();
|
||||
}
|
||||
if (type === 'publisherStopSpeaking') {
|
||||
this.stream.enableOnceHarkStoppedSpeakingEvent();
|
||||
}
|
||||
if (type === 'streamAudioVolumeChange') {
|
||||
this.stream.enableOnceHarkVolumeChangeEvent(false);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See [[EventDispatcher.off]]
|
||||
*/
|
||||
off(type: string, handler?: (event: Event) => void): StreamManager {
|
||||
|
||||
super.off(type, handler);
|
||||
|
||||
if (type === 'publisherStartSpeaking') {
|
||||
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners
|
||||
const remainingStartSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
||||
if (remainingStartSpeakingEventListeners === 0) {
|
||||
this.stream.disableHarkSpeakingEvent(false);
|
||||
}
|
||||
}
|
||||
if (type === 'publisherStopSpeaking') {
|
||||
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners
|
||||
const remainingStopSpeakingEventListeners = this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
|
||||
if (remainingStopSpeakingEventListeners === 0) {
|
||||
this.stream.disableHarkStoppedSpeakingEvent(false);
|
||||
}
|
||||
}
|
||||
if (type === 'streamAudioVolumeChange') {
|
||||
// Only StreamManager can have "streamAudioVolumeChange" event listeners
|
||||
const remainingVolumeEventListeners = this.ee.getListeners(type).length;
|
||||
if (remainingVolumeEventListeners === 0) {
|
||||
this.stream.disableHarkVolumeChangeEvent(false);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes `video` element parameter display this [[stream]]. This is useful when you are
|
||||
* [managing the video elements on your own](/en/stable/cheatsheet/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.remote && 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
|
||||
});
|
||||
|
||||
logger.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 = this.createVideo();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the current configuration for the [[PublisherSpeakingEvent]] feature and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/streammanagerevent.html) feature for this specific
|
||||
* StreamManager audio stream, overriding the global options set with [[OpenVidu.setAdvancedConfiguration]]. This way you can customize the audio events options
|
||||
* for each specific StreamManager and change them dynamically.
|
||||
*
|
||||
* @param publisherSpeakingEventsOptions New options to be applied to this StreamManager's audio stream. 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/stopped or audio volume has changed. Default **100** (ms)
|
||||
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
|
||||
*/
|
||||
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number, threshold?: number }): void {
|
||||
const currentHarkOptions = !!this.stream.harkOptions ? this.stream.harkOptions : (this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {});
|
||||
const newInterval = (typeof publisherSpeakingEventsOptions.interval === 'number') ?
|
||||
publisherSpeakingEventsOptions.interval : ((typeof currentHarkOptions.interval === 'number') ? currentHarkOptions.interval : 100);
|
||||
const newThreshold = (typeof publisherSpeakingEventsOptions.threshold === 'number') ?
|
||||
publisherSpeakingEventsOptions.threshold : ((typeof currentHarkOptions.threshold === 'number') ? currentHarkOptions.threshold : -50);
|
||||
this.stream.harkOptions = {
|
||||
interval: newInterval,
|
||||
threshold: newThreshold
|
||||
};
|
||||
if (!!this.stream.speechEvent) {
|
||||
this.stream.speechEvent.setInterval(newInterval);
|
||||
this.stream.speechEvent.setThreshold(newThreshold);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hidden methods */
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initializeVideoProperties(video: HTMLVideoElement): void {
|
||||
if (!(!this.remote && 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.isSafariBrowser() || (platform.isIPhoneOrIPad() && (platform.isChromeMobileBrowser() || platform.isEdgeMobileBrowser() || platform.isOperaMobileBrowser() || platform.isFirefoxMobileBrowser()))) {
|
||||
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)
|
||||
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
|
||||
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
|
||||
this.removeSrcObject(streamManagerVideo);
|
||||
// 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;
|
||||
logger.info('Video element disassociated from ', this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return disassociated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
addPlayEventToFirstVideo() {
|
||||
if ((!!this.videos[0]) && (!!this.videos[0].video) && (!this.videos[0].canplayListenerAdded)) {
|
||||
this.activateStreamPlayingEventExceptionTimeout();
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
createVideo(): HTMLVideoElement {
|
||||
return document.createElement('video');
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
|
||||
streamManagerVideo.video.srcObject = null;
|
||||
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||
}
|
||||
|
||||
/* Private methods */
|
||||
|
||||
protected 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';
|
||||
}
|
||||
|
||||
private activateStreamPlayingEventExceptionTimeout() {
|
||||
if (!this.remote) {
|
||||
// ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
|
||||
return;
|
||||
}
|
||||
if (this.streamPlayingEventExceptionTimeout != null) {
|
||||
// The timeout is already activated
|
||||
return;
|
||||
}
|
||||
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
|
||||
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
|
||||
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
|
||||
const msg = 'StreamManager of Stream ' + this.stream.streamId + ' (' + (this.remote ? 'Subscriber' : 'Publisher') + ') did not trigger "streamPlaying" event in ' + msTimeout + ' ms';
|
||||
logger.warn(msg);
|
||||
this.stream.session.emitEvent('exception', [new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)]);
|
||||
delete this.streamPlayingEventExceptionTimeout;
|
||||
}, msTimeout);
|
||||
}
|
||||
|
||||
private deactivateStreamPlayingEventExceptionTimeout() {
|
||||
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
|
||||
delete this.streamPlayingEventExceptionTimeout;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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';
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with [[Session.subscribe]] method
|
||||
*
|
||||
* ### Available event listeners (and events dispatched)
|
||||
*
|
||||
* - _All events inherited from [[StreamManager]] class_
|
||||
*/
|
||||
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;
|
||||
});
|
||||
this.stream.audioActive = value;
|
||||
logger.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;
|
||||
});
|
||||
this.stream.videoActive = value;
|
||||
logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its video stream');
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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'
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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.
|
||||
* This error can also be triggered when the user granted permission to use the devices but a hardware
|
||||
* error occurred at the OS, browser or web page level, which prevented access to the device.
|
||||
* 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]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
|
||||
|
||||
/**
|
||||
* Only for Chrome, there's no screen sharing extension installed
|
||||
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
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]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED',
|
||||
|
||||
/**
|
||||
* No video input device found with the provided deviceId (property [[PublisherProperties.videoSource]])
|
||||
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
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]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
INPUT_AUDIO_DEVICE_NOT_FOUND = 'INPUT_AUDIO_DEVICE_NOT_FOUND',
|
||||
|
||||
/**
|
||||
* There was an unknown error when trying to access the specified audio device
|
||||
* Returned upon unsuccessful [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
INPUT_AUDIO_DEVICE_GENERIC_ERROR = 'INPUT_AUDIO_DEVICE_GENERIC_ERROR',
|
||||
|
||||
/**
|
||||
* Method [[OpenVidu.initPublisher]] or [[OpenVidu.getUserMedia]] 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]] or [[OpenVidu.getUserMedia]]
|
||||
*/
|
||||
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',
|
||||
|
||||
/**
|
||||
* There is no connection to the Session. This error will be thrown when any method requiring a connection to
|
||||
* openvidu-server is called before successfully calling method [[Session.connect]]
|
||||
*/
|
||||
OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED',
|
||||
|
||||
/**
|
||||
* Generic error
|
||||
*/
|
||||
GENERIC_ERROR = 'GENERIC_ERROR'
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple object to identify runtime errors on the client side
|
||||
*/
|
||||
export class OpenViduError {
|
||||
|
||||
/**
|
||||
* Uniquely identifying name of the error
|
||||
*/
|
||||
name: OpenViduErrorName;
|
||||
|
||||
/**
|
||||
* Full description of the error
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(name: OpenViduErrorName, message: string) {
|
||||
this.name = name;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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'
|
||||
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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]] after a new user has connected to the session
|
||||
* - `connectionDestroyed`: dispatched by [[Session]] after a new user has left the 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
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* 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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { Session } from '../../OpenVidu/Session';
|
||||
import { Event } from './Event';
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu Pro tier** <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a>
|
||||
*
|
||||
* Defines event `connectionPropertyChanged` dispatched by [[Session]] object.
|
||||
* This event is fired when any property of the local [[Connection]] object changes.
|
||||
* The properties that may change are [[Connection.role]] and [[Connection.record]].
|
||||
*
|
||||
* The only way the Connection properties may change is by updating them through:
|
||||
*
|
||||
* - [API REST](/en/stable/reference-docs/REST-API/#patch-openviduapisessionsltsession_idgtconnectionltconnection_idgt)
|
||||
* - [openvidu-java-client](/en/stable/reference-docs/openvidu-java-client/#update-a-connection)
|
||||
* - [openvidu-node-client](/en/stable/reference-docs/openvidu-node-client/#update-a-connection)<br><br>
|
||||
*/
|
||||
export class ConnectionPropertyChangedEvent extends Event {
|
||||
|
||||
/**
|
||||
* The Connection whose property has changed
|
||||
*/
|
||||
connection: Connection;
|
||||
|
||||
/**
|
||||
* The property of the stream that changed. This value is either `"role"` or `"record"`
|
||||
*/
|
||||
changedProperty: 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, connection: Connection, changedProperty: string, newValue: Object, oldValue: Object) {
|
||||
super(false, target, 'connectionPropertyChanged');
|
||||
this.connection = connection;
|
||||
this.changedProperty = changedProperty;
|
||||
this.newValue = newValue;
|
||||
this.oldValue = oldValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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';
|
||||
|
||||
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 RTCPeerConnection 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 RTCPeerConnection 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();
|
||||
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 '../../OpenVidu/Session';
|
||||
import { Stream } from '../../OpenVidu/Stream';
|
||||
import { Subscriber } from '../../OpenVidu/Subscriber';
|
||||
import { Event } from './Event';
|
||||
|
||||
|
||||
/**
|
||||
* Defines property [[ExceptionEvent.name]]
|
||||
*/
|
||||
export enum ExceptionEventName {
|
||||
|
||||
/**
|
||||
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Session]] object.
|
||||
*/
|
||||
ICE_CANDIDATE_ERROR = 'ICE_CANDIDATE_ERROR',
|
||||
|
||||
/**
|
||||
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
|
||||
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status.
|
||||
*
|
||||
* This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server,
|
||||
* then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to
|
||||
* a total network drop, then no automatic reconnection process will be possible.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
|
||||
*/
|
||||
ICE_CONNECTION_FAILED = 'ICE_CONNECTION_FAILED',
|
||||
|
||||
/**
|
||||
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
|
||||
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status.
|
||||
*
|
||||
* This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to
|
||||
* OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic
|
||||
* reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no
|
||||
* automatic reconnection process will be possible.
|
||||
*
|
||||
* You can customize the timeout for the reconnection attempt with property [[OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout]],
|
||||
* which by default is 4000 milliseconds.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Stream]] object.
|
||||
*/
|
||||
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED',
|
||||
|
||||
/**
|
||||
* A [[Subscriber]] object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to [[StreamManagerEvent]]
|
||||
* category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event).
|
||||
*
|
||||
* OpenVidu Browser can take care of the video players (see [here](/en/latest/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)),
|
||||
* or you can take care of video players on your own (see [here](/en/latest/cheatsheet/manage-videos/#you-take-care-of-the-video-players)).
|
||||
* Either way, whenever a [[Subscriber]] object is commanded to attach its [[Stream]] to a video element, it is supposed to fire `streamPlaying`
|
||||
* event shortly after. If it does not, then we can safely assume that something wrong has happened while playing the remote video and the
|
||||
* application may be notified through this specific ExceptionEvent.
|
||||
*
|
||||
* The timeout can be configured with property [[OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout]]. By default it is 4000 milliseconds.
|
||||
*
|
||||
* This is just an informative exception. It only means that a remote Stream that is supposed to be playing by a video player has not done so
|
||||
* in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin
|
||||
* of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with
|
||||
* autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected.
|
||||
*
|
||||
* [[ExceptionEvent]] objects with this [[ExceptionEvent.name]] will have as [[ExceptionEvent.origin]] property a [[Subscriber]] object.
|
||||
*/
|
||||
NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT'
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines event `exception` dispatched by [[Session]] object.
|
||||
*
|
||||
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins. To see the different
|
||||
* types of exceptions go to [[ExceptionEventName]].
|
||||
*/
|
||||
export class ExceptionEvent extends Event {
|
||||
|
||||
/**
|
||||
* Name of the exception
|
||||
*/
|
||||
name: ExceptionEventName;
|
||||
|
||||
/**
|
||||
* Object affected by the exception. Depending on the [[ExceptionEvent.name]] property:
|
||||
* - [[Session]]: `ICE_CANDIDATE_ERROR`
|
||||
* - [[Stream]]: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
|
||||
* - [[Subscriber]]: `NO_STREAM_PLAYING_EVENT`
|
||||
*/
|
||||
origin: Session | Stream | Subscriber;
|
||||
|
||||
/**
|
||||
* Informative description of the exception
|
||||
*/
|
||||
message: string;
|
||||
|
||||
/**
|
||||
* Any extra information associated to the exception
|
||||
*/
|
||||
data?: any;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | Subscriber, message: string, data?: any) {
|
||||
super(false, session, 'exception');
|
||||
this.name = name;
|
||||
this.origin = origin;
|
||||
this.message = message;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { 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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { Connection } from '../../OpenVidu/Connection';
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu Pro tier** <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" target="_blank" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a>
|
||||
*
|
||||
* Defines event `networkQualityLevelChanged` dispatched by [[Session]].
|
||||
* This event is fired when the network quality level of a [[Connection]] changes. See [network quality](/en/stable/advanced-features/network-quality/)
|
||||
*/
|
||||
export class NetworkQualityLevelChangedEvent extends Event {
|
||||
|
||||
/**
|
||||
* New value of the network quality level
|
||||
*/
|
||||
newValue: number;
|
||||
|
||||
/**
|
||||
* Old value of the network quality level
|
||||
*/
|
||||
oldValue: number;
|
||||
|
||||
/**
|
||||
* Connection for whom the network quality level changed
|
||||
*/
|
||||
connection: Connection
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, newValue: number, oldValue: number, connection: Connection) {
|
||||
super(false, target, 'networkQualityLevelChanged');
|
||||
this.newValue = newValue;
|
||||
this.oldValue = oldValue;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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';
|
||||
import { StreamManager } from '../../OpenVidu/StreamManager';
|
||||
|
||||
|
||||
/**
|
||||
* Defines the following events:
|
||||
* - `publisherStartSpeaking`: dispatched by [[Session]] and [[StreamManager]] when a user has started speaking
|
||||
* - `publisherStopSpeaking`: dispatched by [[Session]] and [[StreamManager]] when a user has stopped speaking
|
||||
*
|
||||
* More information:
|
||||
* - This events will only be triggered for **streams that have audio tracks** ([[Stream.hasAudio]] must be true)
|
||||
* - 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 | StreamManager, 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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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]] after the session has started being recorded
|
||||
* - `recordingStopped`: dispatched by [[Session]] after the session has stopped being recorded
|
||||
*/
|
||||
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](/en/stable/advanced-features/recording/#automatic-stop-of-recordings)
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* 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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
|
||||
/**
|
||||
* Defines event `sessionDisconnected` dispatched by [[Session]] after the local user has left the session. This is the local version of the `connectionDestroyed` event, which is only dispatched by remote users
|
||||
*/
|
||||
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. Before a SessionDisconnectedEvent with this reason is triggered,
|
||||
* Session object will always have previously dispatched a `reconnecting` event. If the reconnection process succeeds,
|
||||
* Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent
|
||||
* with reason "networkDisconnect"
|
||||
* - "nodeCrashed": a node has crashed in the server side. You can use this reason to ask your application's backend to reconnect
|
||||
* to a new session to replace the crashed one
|
||||
*/
|
||||
reason: string;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, reason: string) {
|
||||
super(true, target, 'sessionDisconnected');
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
callDefaultBehavior() {
|
||||
|
||||
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
||||
|
||||
const session = <Session>this.target;
|
||||
|
||||
// Dispose and delete all remote Connections
|
||||
session.remoteConnections.forEach(remoteConnection => {
|
||||
const connectionId = remoteConnection.connectionId;
|
||||
if (!!session.remoteConnections.get(connectionId)?.stream) {
|
||||
session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
|
||||
session.remoteConnections.get(connectionId)?.stream!.disposeMediaStream();
|
||||
if (session.remoteConnections.get(connectionId)?.stream!.streamManager) {
|
||||
session.remoteConnections.get(connectionId)?.stream!.streamManager.removeAllVideos();
|
||||
}
|
||||
const streamId = session.remoteConnections.get(connectionId)?.stream?.streamId;
|
||||
if (!!streamId) {
|
||||
session.remoteStreamsCreated.delete(streamId);
|
||||
}
|
||||
session.remoteConnections.get(connectionId)?.dispose();
|
||||
}
|
||||
session.remoteConnections.delete(connectionId);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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]] when a signal is received
|
||||
* - `signal:TYPE`: dispatched by [[Session]] when a signal of type TYPE is received
|
||||
*/
|
||||
export class SignalEvent extends Event {
|
||||
|
||||
/**
|
||||
* The type of signal. It is string `"signal"` for those signals sent with no [[SignalOptions.type]] property, and `"signal:type"` if was sent with a
|
||||
* valid [[SignalOptions.type]] property.
|
||||
*
|
||||
* The client must be specifically subscribed to `Session.on('signal:type', function(signalEvent) {...})` to trigger that type of signal.
|
||||
*
|
||||
* Subscribing to `Session.on('signal', function(signalEvent) {...})` will trigger all signals, no matter their type.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* The message of the signal (can be empty)
|
||||
*/
|
||||
data?: string;
|
||||
|
||||
/**
|
||||
* The client that sent the signal. This property is undefined if the signal
|
||||
* was directly generated by the application server (not by other client)
|
||||
*/
|
||||
from?: Connection;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, type?: string, data?: string, from?: Connection) {
|
||||
super(false, target, 'signal');
|
||||
if (!!type) {
|
||||
this.type = 'signal:' + type;
|
||||
}
|
||||
this.data = data;
|
||||
this.from = from;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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';
|
||||
import { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* Defines the following events:
|
||||
* - `streamCreated`: dispatched by [[Session]] and [[Publisher]] after some user has started publishing to the session
|
||||
* - `streamDestroyed`: dispatched by [[Session]] and [[Publisher]] after some user has stopped publishing to the session
|
||||
*/
|
||||
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
|
||||
* - "nodeCrashed": a node has crashed in the server side
|
||||
*
|
||||
* 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
|
||||
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
|
||||
this.stream.disposeWebRtcPeer();
|
||||
} else if (this.target instanceof Publisher) {
|
||||
// Local Stream
|
||||
logger.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
|
||||
this.stream.session.remoteStreamsCreated.delete(this.stream.streamId);
|
||||
|
||||
// Delete StreamOptionsServer from remote Connection
|
||||
const remoteConnection = this.stream.session.remoteConnections.get(this.stream.connection.connectionId);
|
||||
if (!!remoteConnection && !!remoteConnection.remoteOptions) {
|
||||
const streamOptionsServer = remoteConnection.remoteOptions.streams;
|
||||
for (let i = streamOptionsServer.length - 1; i >= 0; --i) {
|
||||
if (streamOptionsServer[i].id === this.stream.streamId) {
|
||||
streamOptionsServer.splice(i, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event)
|
||||
* - `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 100ms)
|
||||
*/
|
||||
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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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"`, `"screenResized"` or `"trackReplaced"`
|
||||
* - For `filter`: `"applyFilter"`, `"execFilterMethod"` or `"removeFilter"`
|
||||
*/
|
||||
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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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](/en/stable/cheatsheet/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() { }
|
||||
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 CustomMediaStreamConstraints {
|
||||
constraints: MediaStreamConstraints;
|
||||
audioTrack: MediaStreamTrack | undefined;
|
||||
videoTrack: MediaStreamTrack | undefined;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { RemoteConnectionOptions } from './RemoteConnectionOptions';
|
||||
|
||||
export interface LocalConnectionOptions {
|
||||
id: string;
|
||||
finalUserId: string;
|
||||
createdAt: number;
|
||||
metadata: string;
|
||||
value: RemoteConnectionOptions[];
|
||||
session: string; // OpenVidu Session identifier
|
||||
sessionId: string; // JSON-RPC session identifier
|
||||
role: string;
|
||||
record: boolean;
|
||||
coturnIp: string;
|
||||
turnUsername: string;
|
||||
turnCredential: string;
|
||||
version: string;
|
||||
mediaServer: string;
|
||||
life: number;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 RemoteConnectionOptions {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
metadata: string;
|
||||
streams: StreamOptionsServer[];
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/streammanagerevent.html) 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/stopped or audio volume has changed. Default **100** (ms)
|
||||
* - `threshold`: (number) the volume at which _publisherStartSpeaking_ and _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
|
||||
*
|
||||
* This sets the global default configuration that will affect all streams, but you can later customize these values for each specific stream by calling [[StreamManager.updatePublisherSpeakingEventsOptions]]
|
||||
*/
|
||||
publisherSpeakingEventsOptions?: {
|
||||
interval?: number;
|
||||
threshold?: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Determines the automatic reconnection process policy. Whenever the client's network drops, OpenVidu Browser starts a reconnection process with OpenVidu Server. After network is recovered, OpenVidu Browser automatically
|
||||
* inspects all of its media streams to see their status. For any of them that are broken, it asks OpenVidu Server for a forced and silent reconnection.
|
||||
*
|
||||
* This policy is technically enough to recover any broken media connection after a network drop, but in practice it has been proven that OpenVidu Browser may think a media connection has properly recovered when in fact it has not.
|
||||
* This is not a common case, and it only affects Publisher streams, but it may occur. This property allows **forcing OpenVidu Browser to reconnect all of its outgoing media streams** after a network drop regardless of their supposed status.
|
||||
*
|
||||
* Default to `false`.
|
||||
*/
|
||||
forceMediaReconnectionAfterNetworkDrop?: boolean;
|
||||
|
||||
/**
|
||||
* The milliseconds that must elapse after triggering [[ExceptionEvent]] of name [`ICE_CONNECTION_DISCONNECTED`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#ice_connection_disconnected) to perform an automatic reconnection process of the affected media stream.
|
||||
* This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop,
|
||||
* then no reconnection process will be possible at all.
|
||||
*
|
||||
* Default to `4000`.
|
||||
*/
|
||||
iceConnectionDisconnectedExceptionTimeout?: number;
|
||||
|
||||
/**
|
||||
* The milliseconds that must elapse for the [[ExceptionEvent]] of name [`NO_STREAM_PLAYING_EVENT`](/en/latest/api/openvidu-browser/enums/exceptioneventname.html#no_stream_playing_event) to be fired.
|
||||
*
|
||||
* Default to `4000`.
|
||||
*/
|
||||
noStreamPlayingEventExceptionTimeout?: number;
|
||||
|
||||
/**
|
||||
* Whether to enable simulcast for Publishers or not.
|
||||
*
|
||||
* Default to `false`.
|
||||
*/
|
||||
enableSimulcastExperimental?: boolean;
|
||||
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 (<66) `"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;
|
||||
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
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;
|
|
@ -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;
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* (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');
|
||||
var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger;
|
||||
|
||||
Date.now = Date.now || function () {
|
||||
return +new Date;
|
||||
};
|
||||
|
||||
var PING_INTERVAL = 5000;
|
||||
|
||||
var RECONNECTING = 'RECONNECTING';
|
||||
var CONNECTED = 'CONNECTED';
|
||||
var DISCONNECTED = 'DISCONNECTED';
|
||||
|
||||
var Logger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
*
|
||||
* heartbeat: interval in ms for each heartbeat message,
|
||||
* <pre>
|
||||
* ws : {
|
||||
* uri : URI to conntect to,
|
||||
* 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) {
|
||||
|
||||
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;
|
||||
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();
|
||||
}
|
||||
|
||||
this.getReadyState = function () {
|
||||
return ws.getReadyState();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
module.exports = JsonRpcClient;
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* (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;
|
|
@ -0,0 +1,164 @@
|
|||
/*
|
||||
* (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 OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
|
||||
var Logger = OpenViduLogger.getInstance();
|
||||
|
||||
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,
|
||||
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 reconnecting = false;
|
||||
|
||||
var ws = new WebSocket(wsUri);
|
||||
|
||||
ws.onopen = () => {
|
||||
Logger.debug("WebSocket connected to " + wsUri);
|
||||
if (config.onconnected) {
|
||||
config.onconnected();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = error => {
|
||||
Logger.error(
|
||||
"Could not connect to " + wsUri + " (invoking onerror if defined)",
|
||||
error
|
||||
);
|
||||
if (config.onerror) {
|
||||
config.onerror(error);
|
||||
}
|
||||
};
|
||||
|
||||
var reconnectionOnClose = () => {
|
||||
if (ws.readyState === CLOSED) {
|
||||
if (closing) {
|
||||
Logger.debug("Connection closed by user");
|
||||
} else {
|
||||
if (config.ismasternodecrashed()) {
|
||||
Logger.error("Master Node has crashed. Stopping reconnection process");
|
||||
} else {
|
||||
Logger.debug("Connection closed unexpectedly. Reconnecting...");
|
||||
reconnect(MAX_RETRIES, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Logger.debug("Close callback from previous websocket. Ignoring it");
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = reconnectionOnClose;
|
||||
|
||||
function reconnect(maxRetries, numRetries) {
|
||||
Logger.debug(
|
||||
"reconnect (attempt #" + numRetries + ", max=" + maxRetries + ")"
|
||||
);
|
||||
if (numRetries === 1) {
|
||||
if (reconnecting) {
|
||||
Logger.warn(
|
||||
"Trying to reconnect when already reconnecting... Ignoring this reconnection."
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
reconnecting = true;
|
||||
}
|
||||
if (config.onreconnecting) {
|
||||
config.onreconnecting();
|
||||
}
|
||||
}
|
||||
reconnectAux(maxRetries, numRetries);
|
||||
}
|
||||
|
||||
function reconnectAux(maxRetries, numRetries) {
|
||||
Logger.debug("Reconnection attempt #" + numRetries);
|
||||
ws.close();
|
||||
ws = new WebSocket(wsUri);
|
||||
|
||||
ws.onopen = () => {
|
||||
Logger.debug(
|
||||
"Reconnected to " + wsUri + " after " + numRetries + " attempts..."
|
||||
);
|
||||
reconnecting = false;
|
||||
registerMessageHandler();
|
||||
if (config.onreconnected()) {
|
||||
config.onreconnected();
|
||||
}
|
||||
ws.onclose = reconnectionOnClose;
|
||||
};
|
||||
|
||||
ws.onerror = error => {
|
||||
Logger.warn("Reconnection error: ", error);
|
||||
if (numRetries === maxRetries) {
|
||||
if (config.ondisconnect) {
|
||||
config.ondisconnect();
|
||||
}
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
reconnect(maxRetries, numRetries + 1);
|
||||
}, RETRY_TIME_MS);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
this.close = () => {
|
||||
closing = true;
|
||||
ws.close();
|
||||
};
|
||||
|
||||
this.forceClose = () => {
|
||||
ws.close();
|
||||
};
|
||||
|
||||
this.reconnectWs = () => {
|
||||
Logger.debug("reconnectWs");
|
||||
reconnect(MAX_RETRIES, 1);
|
||||
};
|
||||
|
||||
this.send = message => {
|
||||
ws.send(message);
|
||||
};
|
||||
|
||||
this.addEventListener = (type, callback) => {
|
||||
registerMessageHandler = () => {
|
||||
ws.addEventListener(type, callback);
|
||||
};
|
||||
registerMessageHandler();
|
||||
};
|
||||
|
||||
this.getReadyState = () => {
|
||||
return ws.readyState;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WebSocketWithReconnection;
|
|
@ -0,0 +1,754 @@
|
|||
/*
|
||||
* (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;
|
|
@ -0,0 +1,95 @@
|
|||
/**
|
||||
* 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;
|
|
@ -0,0 +1,10 @@
|
|||
function pack(message) {
|
||||
throw new TypeError("Not yet implemented");
|
||||
};
|
||||
|
||||
function unpack(message) {
|
||||
throw new TypeError("Not yet implemented");
|
||||
};
|
||||
|
||||
exports.pack = pack;
|
||||
exports.unpack = unpack;
|
|
@ -0,0 +1,6 @@
|
|||
var JsonRPC = require('./JsonRPC');
|
||||
var XmlRPC = require('./XmlRPC');
|
||||
|
||||
|
||||
exports.JsonRPC = JsonRPC;
|
||||
exports.XmlRPC = XmlRPC;
|
|
@ -0,0 +1,42 @@
|
|||
type ConsoleFunction = (...data: any) => void;
|
||||
export class ConsoleLogger {
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
logger: Console
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
log: ConsoleFunction
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
info: ConsoleFunction
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
debug: ConsoleFunction
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
warn: ConsoleFunction
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
error: ConsoleFunction
|
||||
|
||||
constructor(console: Console) {
|
||||
this.logger = console;
|
||||
this.log = window.console.log,
|
||||
this.info = window.console.info,
|
||||
this.debug = window.console.debug,
|
||||
this.warn = window.console.warn,
|
||||
this.error = window.console.error
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
import { JL } from 'jsnlog'
|
||||
import { OpenVidu } from "../../OpenVidu/OpenVidu";
|
||||
import { ConsoleLogger } from './ConsoleLogger';
|
||||
import { OpenViduLoggerConfiguration } from "./OpenViduLoggerConfiguration";
|
||||
|
||||
export class OpenViduLogger {
|
||||
|
||||
private static instance: OpenViduLogger;
|
||||
|
||||
private JSNLOG_URL: string = "/openvidu/elk/openvidu-browser-logs";
|
||||
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
|
||||
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
|
||||
private MAX_LENGTH_STRING_JSON: number = 1000;
|
||||
|
||||
private defaultConsoleLogger: ConsoleLogger = new ConsoleLogger(window.console);
|
||||
|
||||
private currentAppender: any;
|
||||
|
||||
private isProdMode = false;
|
||||
private isJSNLogSetup = false;
|
||||
|
||||
// This two variables are used to restart JSNLog
|
||||
// on different sessions and different userIds
|
||||
private loggingSessionId: string | undefined;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
static configureJSNLog(openVidu: OpenVidu, token: string) {
|
||||
try {
|
||||
// If dev mode or...
|
||||
if ((window['LOG_JSNLOG_RESULTS']) ||
|
||||
// If instance is created and it is OpenVidu Pro
|
||||
(this.instance && openVidu.isPro
|
||||
// If logs are enabled
|
||||
&& this.instance.isOpenViduBrowserLogsDebugActive(openVidu)
|
||||
// Only reconfigure it if session or finalUserId has changed
|
||||
&& this.instance.canConfigureJSNLog(openVidu, this.instance))) {
|
||||
|
||||
// Check if app logs can be sent
|
||||
// and replace console.log function to send
|
||||
// logs of the application
|
||||
if (openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app) {
|
||||
this.instance.replaceWindowConsole();
|
||||
}
|
||||
|
||||
// isJSNLogSetup will not be true until completed setup
|
||||
this.instance.isJSNLogSetup = false;
|
||||
this.instance.info("Configuring JSNLogs.");
|
||||
|
||||
const finalUserId = openVidu.finalUserId;
|
||||
const sessionId = openVidu.session.sessionId;
|
||||
|
||||
const beforeSendCallback = (xhr) => {
|
||||
// If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs
|
||||
// https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818
|
||||
const parentReadyStateFunction = xhr.onreadystatechange;
|
||||
xhr.onreadystatechange = () => {
|
||||
if (this.isInvalidResponse(xhr)) {
|
||||
Object.defineProperty(xhr, "readyState", { value: 4 });
|
||||
Object.defineProperty(xhr, "status", { value: 200 });
|
||||
// Disable JSNLog too to not send periodically errors
|
||||
this.instance.disableLogger();
|
||||
}
|
||||
parentReadyStateFunction();
|
||||
}
|
||||
|
||||
// Headers to identify and authenticate logs
|
||||
xhr.setRequestHeader('Authorization', "Basic " + btoa(`${finalUserId}%/%${sessionId}` + ":" + token));
|
||||
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest')
|
||||
// Additional headers for OpenVidu
|
||||
xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
|
||||
xhr.setRequestHeader('OV-Session-Id', sessionId);
|
||||
xhr.setRequestHeader('OV-Token', token);
|
||||
}
|
||||
|
||||
// Creation of the appender.
|
||||
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
|
||||
this.instance.currentAppender.setOptions({
|
||||
beforeSend: beforeSendCallback,
|
||||
maxBatchSize: 1000,
|
||||
batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
|
||||
batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
|
||||
});
|
||||
|
||||
// Avoid circular dependencies
|
||||
const logSerializer = (obj): string => {
|
||||
const getCircularReplacer = () => {
|
||||
const seen = new WeakSet();
|
||||
return (key, value) => {
|
||||
if (typeof value === "object" && value != null) {
|
||||
if (seen.has(value) || (HTMLElement && value instanceof HTMLElement)) {
|
||||
return;
|
||||
}
|
||||
seen.add(value);
|
||||
}
|
||||
return value;
|
||||
};
|
||||
};
|
||||
|
||||
// Cut long messages
|
||||
let stringifyJson = JSON.stringify(obj, getCircularReplacer());
|
||||
if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) {
|
||||
stringifyJson = `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`;
|
||||
}
|
||||
|
||||
if (window['LOG_JSNLOG_RESULTS']) {
|
||||
console.log(stringifyJson);
|
||||
}
|
||||
|
||||
return stringifyJson;
|
||||
};
|
||||
|
||||
// Initialize JL to send logs
|
||||
JL.setOptions({
|
||||
defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL,
|
||||
serialize: logSerializer,
|
||||
enabled: true
|
||||
});
|
||||
JL().setOptions({
|
||||
appenders: [this.instance.currentAppender]
|
||||
});
|
||||
|
||||
this.instance.isJSNLogSetup = true;
|
||||
this.instance.loggingSessionId = sessionId;
|
||||
this.instance.info("JSNLog configured.");
|
||||
}
|
||||
} catch (e) {
|
||||
// Print error
|
||||
console.error("Error configuring JSNLog: ");
|
||||
console.error(e);
|
||||
// Restore defaults values just in case any exception happen-
|
||||
this.instance.disableLogger();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
static getInstance(): OpenViduLogger {
|
||||
if (!OpenViduLogger.instance) {
|
||||
OpenViduLogger.instance = new OpenViduLogger();
|
||||
}
|
||||
return OpenViduLogger.instance;
|
||||
}
|
||||
|
||||
private static isInvalidResponse(xhr: XMLHttpRequest) {
|
||||
return xhr.status == 401 || xhr.status == 403 || xhr.status == 404 || xhr.status == 0;
|
||||
}
|
||||
|
||||
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
|
||||
return openVidu.session.sessionId != logger.loggingSessionId
|
||||
}
|
||||
|
||||
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
|
||||
return openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
|
||||
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app;
|
||||
}
|
||||
|
||||
// Return console functions with jsnlog integration
|
||||
private getConsoleWithJSNLog() {
|
||||
return function (openViduLogger: OpenViduLogger) {
|
||||
return {
|
||||
log: function (...args) {
|
||||
openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||
if (openViduLogger.isJSNLogSetup) {
|
||||
JL().info(arguments);
|
||||
}
|
||||
},
|
||||
info: function (...args) {
|
||||
openViduLogger.defaultConsoleLogger.info.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||
if (openViduLogger.isJSNLogSetup) {
|
||||
JL().info(arguments);
|
||||
}
|
||||
},
|
||||
debug: function (...args) {
|
||||
openViduLogger.defaultConsoleLogger.debug.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||
},
|
||||
warn: function (...args) {
|
||||
openViduLogger.defaultConsoleLogger.warn.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||
if (openViduLogger.isJSNLogSetup) {
|
||||
JL().warn(arguments);
|
||||
}
|
||||
},
|
||||
error: function (...args) {
|
||||
openViduLogger.defaultConsoleLogger.error.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
|
||||
if (openViduLogger.isJSNLogSetup) {
|
||||
JL().error(arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
}(this);
|
||||
}
|
||||
|
||||
private replaceWindowConsole() {
|
||||
window.console = this.defaultConsoleLogger.logger;
|
||||
window.console.log = this.getConsoleWithJSNLog().log;
|
||||
window.console.info = this.getConsoleWithJSNLog().info;
|
||||
window.console.debug = this.getConsoleWithJSNLog().debug;
|
||||
window.console.warn = this.getConsoleWithJSNLog().warn;
|
||||
window.console.error = this.getConsoleWithJSNLog().error;
|
||||
}
|
||||
|
||||
private disableLogger() {
|
||||
JL.setOptions({ enabled: false });
|
||||
this.isJSNLogSetup = false;
|
||||
this.loggingSessionId = undefined;
|
||||
this.currentAppender = undefined;
|
||||
window.console = this.defaultConsoleLogger.logger;
|
||||
window.console.log = this.defaultConsoleLogger.log;
|
||||
window.console.info = this.defaultConsoleLogger.info;
|
||||
window.console.debug = this.defaultConsoleLogger.debug;
|
||||
window.console.warn = this.defaultConsoleLogger.warn;
|
||||
window.console.error = this.defaultConsoleLogger.error;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
log(...args: any[]) {
|
||||
if (!this.isProdMode) {
|
||||
this.defaultConsoleLogger.log.apply(this.defaultConsoleLogger.logger, arguments);
|
||||
}
|
||||
if (this.isJSNLogSetup) {
|
||||
JL().info(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
debug(...args: any[]) {
|
||||
if (!this.isProdMode) {
|
||||
this.defaultConsoleLogger.debug.apply(this.defaultConsoleLogger.logger, arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
info(...args: any[]) {
|
||||
if (!this.isProdMode) {
|
||||
this.defaultConsoleLogger.info.apply(this.defaultConsoleLogger.logger, arguments);
|
||||
}
|
||||
if (this.isJSNLogSetup) {
|
||||
JL().info(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
warn(...args: any[]) {
|
||||
if (!this.isProdMode) {
|
||||
this.defaultConsoleLogger.warn.apply(this.defaultConsoleLogger.logger, arguments);
|
||||
}
|
||||
if (this.isJSNLogSetup) {
|
||||
JL().warn(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
error(...args: any[]) {
|
||||
this.defaultConsoleLogger.error.apply(this.defaultConsoleLogger.logger, arguments);
|
||||
if (this.isJSNLogSetup) {
|
||||
JL().error(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
flush() {
|
||||
if (this.isJSNLogSetup && this.currentAppender != null) {
|
||||
this.currentAppender.sendBatch();
|
||||
}
|
||||
}
|
||||
|
||||
enableProdMode() {
|
||||
this.isProdMode = true;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export enum OpenViduLoggerConfiguration {
|
||||
disabled = 'disabled',
|
||||
debug = 'debug',
|
||||
debug_app = 'debug_app'
|
||||
}
|
|
@ -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 = window.getScreenId;
|
|
@ -0,0 +1,167 @@
|
|||
// global variables
|
||||
var chromeMediaSource = 'screen';
|
||||
var sourceId;
|
||||
var screenCallback;
|
||||
|
||||
if(typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined'){
|
||||
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;
|
|
@ -0,0 +1,215 @@
|
|||
import platform = require("platform");
|
||||
|
||||
export class PlatformUtils {
|
||||
protected static instance: PlatformUtils;
|
||||
constructor() { }
|
||||
|
||||
static getInstance(): PlatformUtils {
|
||||
if (!this.instance) {
|
||||
this.instance = new PlatformUtils();
|
||||
}
|
||||
return PlatformUtils.instance;
|
||||
}
|
||||
|
||||
public isChromeBrowser(): boolean {
|
||||
return platform.name === "Chrome";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isSafariBrowser(): boolean {
|
||||
return platform.name === "Safari";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isChromeMobileBrowser(): boolean {
|
||||
return platform.name === "Chrome Mobile";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isFirefoxBrowser(): boolean {
|
||||
return platform.name === "Firefox";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isFirefoxMobileBrowser(): boolean {
|
||||
return platform.name === "Firefox Mobile" || platform.name === "Firefox for iOS";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isOperaBrowser(): boolean {
|
||||
return platform.name === "Opera";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isOperaMobileBrowser(): boolean {
|
||||
return platform.name === "Opera Mobile";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isEdgeBrowser(): boolean {
|
||||
const version = platform?.version ? parseFloat(platform.version) : -1;
|
||||
return platform.name === "Microsoft Edge" && version >= 80;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isEdgeMobileBrowser(): boolean {
|
||||
const version = platform?.version ? parseFloat(platform.version) : -1;
|
||||
return platform.name === "Microsoft Edge" && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isAndroidBrowser(): boolean {
|
||||
return platform.name === "Android Browser";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isElectron(): boolean {
|
||||
return platform.name === "Electron";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isSamsungBrowser(): boolean {
|
||||
return (
|
||||
platform.name === "Samsung Internet Mobile" ||
|
||||
platform.name === "Samsung Internet"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isIPhoneOrIPad(): boolean {
|
||||
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
||||
const isTouchable = "ontouchend" in document;
|
||||
const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable;
|
||||
const isIPhone =
|
||||
/\b(\w*iPhone\w*)\b/.test(userAgent) &&
|
||||
/\b(\w*Mobile\w*)\b/.test(userAgent) &&
|
||||
isTouchable;
|
||||
return isIPad || isIPhone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isIOSWithSafari(): boolean {
|
||||
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
|
||||
return this.isIPhoneOrIPad() && (
|
||||
/\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
|
||||
/\b(\w*Safari\w*)\b/.test(userAgent) &&
|
||||
!/\b(\w*CriOS\w*)\b/.test(userAgent) &&
|
||||
!/\b(\w*FxiOS\w*)\b/.test(userAgent)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isIonicIos(): boolean {
|
||||
return this.isIPhoneOrIPad() && platform.ua!!.indexOf("Safari") === -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isIonicAndroid(): boolean {
|
||||
return (
|
||||
platform.os!!.family === "Android" && platform.name == "Android Browser"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isMobileDevice(): boolean {
|
||||
return platform.os!!.family === "iOS" || platform.os!!.family === "Android";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isReactNative(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public isChromium(): boolean {
|
||||
return this.isChromeBrowser() || this.isChromeMobileBrowser() ||
|
||||
this.isOperaBrowser() || this.isOperaMobileBrowser() ||
|
||||
this.isEdgeBrowser() || this.isEdgeMobileBrowser() ||
|
||||
this.isSamsungBrowser() ||
|
||||
this.isIonicAndroid() || this.isIonicIos() ||
|
||||
this.isElectron();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public canScreenShare(): boolean {
|
||||
const version = platform?.version ? parseFloat(platform.version) : -1;
|
||||
// Reject mobile devices
|
||||
if (this.isMobileDevice()) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
this.isChromeBrowser() ||
|
||||
this.isFirefoxBrowser() ||
|
||||
this.isOperaBrowser() ||
|
||||
this.isElectron() ||
|
||||
this.isEdgeBrowser() ||
|
||||
(this.isSafariBrowser() && version >= 13)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public getName(): string {
|
||||
return platform.name || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public getVersion(): string {
|
||||
return platform.version || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public getFamily(): string {
|
||||
return platform.os!!.family || "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
public getDescription(): string {
|
||||
return platform.description || "";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,536 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { ExceptionEventName } from '../Events/ExceptionEvent';
|
||||
import { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../Utils/Platform';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
/*
|
||||
* Table of sender video encodings for simulcast.
|
||||
* Note that this is just a polite request, but the browser is free to honor it
|
||||
* or just play by its own rules.
|
||||
*
|
||||
* Chrome imposes some restrictions based on the size of the video, max bitrate,
|
||||
* and available bandwidth. Check here for the video size table:
|
||||
* https://chromium.googlesource.com/external/webrtc/+/master/media/engine/simulcast.cc#90
|
||||
*
|
||||
* | Size (px) | Bitrate (kbps) | Max Layers |
|
||||
* |----------:|---------------:|-----------:|
|
||||
* | 1920x1080 | 5000 | 3 |
|
||||
* | 1280x720 | 2500 | 3 |
|
||||
* | 960x540 | 1200 | 3 |
|
||||
* | 640x360 | 700 | 2 |
|
||||
* | 480x270 | 450 | 2 |
|
||||
* | 320x180 | 200 | 1 |
|
||||
*
|
||||
* Firefox will send as many layers as we request, but there are some limits on
|
||||
* their bitrate:
|
||||
*
|
||||
* | Size (px) | Min bitrate (bps) | Start bitrate (bps) | Max bitrate (bps) | Comments |
|
||||
* |----------:|------------------:|--------------------:|------------------:|---------------:|
|
||||
* | 1920x1200 | 1500 | 2000 | 10000 | >HD (3K, 4K) |
|
||||
* | 1280x720 | 1200 | 1500 | 5000 | HD ~1080-1200 |
|
||||
* | 800x480 | 200 | 800 | 2500 | HD ~720 |
|
||||
* | 480x270 | 150 | 500 | 2000 | WVGA |
|
||||
* | 400x240 | 125 | 300 | 1300 | VGA |
|
||||
* | 176x144 | 100 | 150 | 500 | WQVGA, CIF |
|
||||
* | 0 | 40 | 80 | 250 | QCIF and below |
|
||||
*
|
||||
* Docs for `RTCRtpEncodingParameters`: https://www.w3.org/TR/webrtc/#dom-rtcrtpencodingparameters
|
||||
* Most interesting members are `maxBitrate` and `scaleResolutionDownBy`.
|
||||
*
|
||||
* `scaleResolutionDownBy` is specified as 4:2:1 which is the same that the default.
|
||||
* The WebRTC spec says this (https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-addtransceiver):
|
||||
* > If the scaleResolutionDownBy attributes of sendEncodings are still undefined, initialize
|
||||
* > each encoding's scaleResolutionDownBy to 2^(length of sendEncodings - encoding index
|
||||
* > - 1). This results in smaller-to-larger resolutions where the last encoding has no scaling
|
||||
* > applied to it, e.g. 4:2:1 if the length is 3.
|
||||
* However, Firefox doesn't seem to implement this default yet. Mediasoup never gets to select
|
||||
* an output layer.
|
||||
*
|
||||
* `maxBitrate` is left unspecified, to let the client decide based on its own
|
||||
* bandwidth limit detection.
|
||||
*/
|
||||
const simulcastVideoEncodings: RTCRtpEncodingParameters[] = [
|
||||
{
|
||||
rid: "r0",
|
||||
scaleResolutionDownBy: 4,
|
||||
},
|
||||
{
|
||||
rid: "r1",
|
||||
scaleResolutionDownBy: 2,
|
||||
},
|
||||
{
|
||||
rid: "r2",
|
||||
scaleResolutionDownBy: 1,
|
||||
},
|
||||
];
|
||||
|
||||
export interface WebRtcPeerConfiguration {
|
||||
mediaConstraints: {
|
||||
audio: boolean,
|
||||
video: boolean
|
||||
};
|
||||
simulcast: boolean;
|
||||
mediaServer: string;
|
||||
onIceCandidate: (event: RTCIceCandidate) => void;
|
||||
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
|
||||
iceServers?: RTCIceServer[];
|
||||
mediaStream?: MediaStream | null;
|
||||
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export class WebRtcPeer {
|
||||
pc: RTCPeerConnection;
|
||||
remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||
localCandidatesQueue: RTCIceCandidate[] = [];
|
||||
|
||||
// Same as WebRtcPeerConfiguration but without optional fields.
|
||||
protected configuration: Required<WebRtcPeerConfiguration>;
|
||||
|
||||
private iceCandidateList: RTCIceCandidate[] = [];
|
||||
private candidategatheringdone = false;
|
||||
|
||||
constructor(configuration: WebRtcPeerConfiguration) {
|
||||
platform = PlatformUtils.getInstance();
|
||||
|
||||
this.configuration = {
|
||||
...configuration,
|
||||
iceServers:
|
||||
!!configuration.iceServers &&
|
||||
configuration.iceServers.length > 0
|
||||
? configuration.iceServers
|
||||
: freeice(),
|
||||
mediaStream:
|
||||
configuration.mediaStream !== undefined
|
||||
? configuration.mediaStream
|
||||
: null,
|
||||
mode: !!configuration.mode ? configuration.mode : "sendrecv",
|
||||
id: !!configuration.id ? configuration.id : this.generateUniqueId(),
|
||||
};
|
||||
|
||||
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate != null) {
|
||||
const candidate: RTCIceCandidate = event.candidate;
|
||||
this.configuration.onIceCandidate(candidate);
|
||||
if (candidate.candidate !== '') {
|
||||
this.localCandidatesQueue.push(<RTCIceCandidate>{ candidate: candidate.candidate });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pc.addEventListener('signalingstatechange', () => {
|
||||
if (this.pc.signalingState === 'stable') {
|
||||
while (this.iceCandidateList.length > 0) {
|
||||
let candidate = this.iceCandidateList.shift();
|
||||
this.pc.addIceCandidate(<RTCIceCandidate>candidate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.configuration.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method frees the resources used by WebRtcPeer
|
||||
*/
|
||||
dispose() {
|
||||
logger.debug('Disposing WebRtcPeer');
|
||||
if (this.pc) {
|
||||
if (this.pc.signalingState === 'closed') {
|
||||
return;
|
||||
}
|
||||
this.pc.close();
|
||||
this.remoteCandidatesQueue = [];
|
||||
this.localCandidatesQueue = [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SDP offer from the local RTCPeerConnection to send to the other peer
|
||||
* Only if the negotiation was initiated by this peer
|
||||
*/
|
||||
createOffer(): Promise<RTCSessionDescriptionInit> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
// TODO: Delete this conditional when all supported browsers are
|
||||
// modern enough to implement the Transceiver methods.
|
||||
if ("addTransceiver" in this.pc) {
|
||||
logger.debug("[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it");
|
||||
|
||||
// Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
|
||||
|
||||
if (this.configuration.mode !== "recvonly") {
|
||||
// To send media, assume that all desired media tracks
|
||||
// have been already added by higher level code to our
|
||||
// MediaStream.
|
||||
|
||||
if (!this.configuration.mediaStream) {
|
||||
reject(new Error(`${this.configuration.mode} direction requested, but no stream was configured to be sent`));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const track of this.configuration.mediaStream.getTracks()) {
|
||||
const tcInit: RTCRtpTransceiverInit = {
|
||||
direction: this.configuration.mode,
|
||||
streams: [this.configuration.mediaStream],
|
||||
};
|
||||
if (this.configuration.simulcast && track.kind === "video") {
|
||||
tcInit.sendEncodings = simulcastVideoEncodings;
|
||||
}
|
||||
const tc = this.pc.addTransceiver(track, tcInit);
|
||||
|
||||
// FIXME: Check that the simulcast encodings were applied.
|
||||
// Firefox doesn't implement `RTCRtpTransceiverInit.sendEncodings`
|
||||
// so the only way to enable simulcast is with `RTCRtpSender.setParameters()`.
|
||||
//
|
||||
// This next block can be deleted when Firefox fixes bug #1396918:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
|
||||
//
|
||||
// NOTE: This is done in a way that is compatible with all browsers, to save on
|
||||
// browser-conditional code. The idea comes from WebRTC Adapter.js:
|
||||
// * https://github.com/webrtcHacks/adapter/issues/998
|
||||
// * https://github.com/webrtcHacks/adapter/blob/845a3b4874f1892a76f04c3cc520e80b5041c303/src/js/firefox/firefox_shim.js#L217
|
||||
if (this.configuration.simulcast && track.kind === "video") {
|
||||
const sendParams = tc.sender.getParameters();
|
||||
if (
|
||||
!("encodings" in sendParams) ||
|
||||
sendParams.encodings.length !== tcInit.sendEncodings!.length
|
||||
) {
|
||||
sendParams.encodings = tcInit.sendEncodings!;
|
||||
await tc.sender.setParameters(sendParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// To just receive media, create new recvonly transceivers.
|
||||
for (const kind of ["audio", "video"]) {
|
||||
// Check if the media kind should be used.
|
||||
if (!this.configuration.mediaConstraints[kind]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this.configuration.mediaStream = new MediaStream();
|
||||
this.pc.addTransceiver(kind, {
|
||||
direction: this.configuration.mode,
|
||||
streams: [this.configuration.mediaStream],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this.pc
|
||||
.createOffer()
|
||||
.then((sdpOffer) => resolve(sdpOffer))
|
||||
.catch((error) => reject(error));
|
||||
} else {
|
||||
logger.warn("[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}");
|
||||
|
||||
// DEPRECATED LEGACY METHOD: Old WebRTC versions don't implement
|
||||
// Transceivers, and instead depend on the deprecated
|
||||
// "offerToReceiveAudio" and "offerToReceiveVideo".
|
||||
|
||||
if (!!this.configuration.mediaStream) {
|
||||
this.deprecatedPeerConnectionTrackApi();
|
||||
}
|
||||
|
||||
const hasAudio = this.configuration.mediaConstraints.audio;
|
||||
const hasVideo = this.configuration.mediaConstraints.video;
|
||||
|
||||
const options: RTCOfferOptions = {
|
||||
offerToReceiveAudio:
|
||||
this.configuration.mode !== "sendonly" && hasAudio,
|
||||
offerToReceiveVideo:
|
||||
this.configuration.mode !== "sendonly" && hasVideo,
|
||||
};
|
||||
|
||||
logger.debug("RTCPeerConnection.createOffer() options:", JSON.stringify(options));
|
||||
|
||||
this.pc
|
||||
// @ts-ignore - Compiler is too clever and thinks this branch will never execute.
|
||||
.createOffer(options)
|
||||
.then((sdpOffer) => resolve(sdpOffer))
|
||||
.catch((error) => reject(error));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
deprecatedPeerConnectionTrackApi() {
|
||||
for (const track of this.configuration.mediaStream!.getTracks()) {
|
||||
this.pc.addTrack(track, this.configuration.mediaStream!);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SDP answer from the local RTCPeerConnection to send to the other peer
|
||||
* Only if the negotiation was initiated by the other peer
|
||||
*/
|
||||
createAnswer(): Promise<RTCSessionDescriptionInit> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// TODO: Delete this conditional when all supported browsers are
|
||||
// modern enough to implement the Transceiver methods.
|
||||
if ("getTransceivers" in this.pc) {
|
||||
logger.debug("[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it");
|
||||
|
||||
// Ensure that the PeerConnection already contains one Transceiver
|
||||
// for each kind of media.
|
||||
// The Transceivers should have been already created internally by
|
||||
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
|
||||
|
||||
for (const kind of ["audio", "video"]) {
|
||||
// Check if the media kind should be used.
|
||||
if (!this.configuration.mediaConstraints[kind]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let tc = this.pc
|
||||
.getTransceivers()
|
||||
.find((tc) => tc.receiver.track.kind === kind);
|
||||
|
||||
if (tc) {
|
||||
// Enforce our desired direction.
|
||||
tc.direction = this.configuration.mode;
|
||||
} else {
|
||||
reject(new Error(`${kind} requested, but no transceiver was created from remote description`));
|
||||
}
|
||||
}
|
||||
|
||||
this.pc
|
||||
.createAnswer()
|
||||
.then((sdpAnswer) => resolve(sdpAnswer))
|
||||
.catch((error) => reject(error));
|
||||
|
||||
} else {
|
||||
|
||||
// TODO: Delete else branch when all supported browsers are
|
||||
// modern enough to implement the Transceiver methods
|
||||
|
||||
let offerAudio, offerVideo = true;
|
||||
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: offerAudio,
|
||||
offerToReceiveVideo: offerVideo
|
||||
};
|
||||
this.pc!.createAnswer(constraints).then(sdpAnswer => {
|
||||
resolve(sdpAnswer);
|
||||
}).catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// else, there is nothing to do; the legacy createAnswer() options do
|
||||
// not offer any control over which tracks are included in the answer.
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This peer initiated negotiation. Step 1/4 of SDP offer-answer protocol
|
||||
*/
|
||||
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.pc.setLocalDescription(offer)
|
||||
.then(() => {
|
||||
const localDescription = this.pc.localDescription;
|
||||
if (!!localDescription) {
|
||||
logger.debug('Local description set', localDescription.sdp);
|
||||
resolve();
|
||||
} else {
|
||||
reject('Local description is not defined');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Other peer initiated negotiation. Step 2/4 of SDP offer-answer protocol
|
||||
*/
|
||||
processRemoteOffer(sdpOffer: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const offer: RTCSessionDescriptionInit = {
|
||||
type: 'offer',
|
||||
sdp: sdpOffer
|
||||
};
|
||||
logger.debug('SDP offer received, setting remote description', offer);
|
||||
|
||||
if (this.pc.signalingState === 'closed') {
|
||||
reject('RTCPeerConnection is closed when trying to set remote description');
|
||||
}
|
||||
this.setRemoteDescription(offer)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch(error => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Other peer initiated negotiation. Step 3/4 of SDP offer-answer protocol
|
||||
*/
|
||||
processLocalAnswer(answer: RTCSessionDescriptionInit): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.debug('SDP answer created, setting local description');
|
||||
if (this.pc.signalingState === 'closed') {
|
||||
reject('RTCPeerConnection is closed when trying to set local description');
|
||||
}
|
||||
this.pc.setLocalDescription(answer)
|
||||
.then(() => resolve())
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This peer initiated negotiation. Step 4/4 of SDP offer-answer protocol
|
||||
*/
|
||||
processRemoteAnswer(sdpAnswer: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const answer: RTCSessionDescriptionInit = {
|
||||
type: 'answer',
|
||||
sdp: sdpAnswer
|
||||
};
|
||||
logger.debug('SDP answer received, setting remote description');
|
||||
|
||||
if (this.pc.signalingState === 'closed') {
|
||||
reject('RTCPeerConnection is closed when trying to set remote description');
|
||||
}
|
||||
this.setRemoteDescription(answer)
|
||||
.then(() => resolve())
|
||||
.catch(error => reject(error));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
async setRemoteDescription(sdp: RTCSessionDescriptionInit): Promise<void> {
|
||||
return this.pc.setRemoteDescription(sdp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback function invoked when an ICE candidate is received
|
||||
*/
|
||||
addIceCandidate(iceCandidate: RTCIceCandidate): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
addIceConnectionStateChangeListener(otherId: string) {
|
||||
this.pc.addEventListener('iceconnectionstatechange', () => {
|
||||
const iceConnectionState: RTCIceConnectionState = this.pc.iceConnectionState;
|
||||
switch (iceConnectionState) {
|
||||
case 'disconnected':
|
||||
// Possible network disconnection
|
||||
const msg1 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "disconnected". Possible network disconnection';
|
||||
logger.warn(msg1);
|
||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
|
||||
break;
|
||||
case 'failed':
|
||||
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
|
||||
logger.error(msg2);
|
||||
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
|
||||
break;
|
||||
case 'closed':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"');
|
||||
break;
|
||||
case 'new':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
|
||||
break;
|
||||
case 'checking':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"');
|
||||
break;
|
||||
case 'connected':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"');
|
||||
break;
|
||||
case 'completed':
|
||||
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"');
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
generateUniqueId(): string {
|
||||
return uuidv4();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 { OpenViduLogger } from '../Logger/OpenViduLogger';
|
||||
import { PlatformUtils } from '../Utils/Platform';
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
interface WebrtcStatsConfig {
|
||||
interval: number,
|
||||
httpEndpoint: string
|
||||
}
|
||||
|
||||
interface JSONStatsResponse {
|
||||
'@timestamp': string,
|
||||
participant_id: string,
|
||||
session_id: string,
|
||||
platform: string,
|
||||
platform_description: string,
|
||||
stream: string,
|
||||
webrtc_stats: IWebrtcStats
|
||||
}
|
||||
|
||||
/**
|
||||
* Common WebRtcSTats for latest Chromium and Firefox versions
|
||||
*/
|
||||
interface IWebrtcStats {
|
||||
inbound?: {
|
||||
audio: {
|
||||
bytesReceived: number,
|
||||
packetsReceived: number,
|
||||
packetsLost: number,
|
||||
jitter: number
|
||||
} | {},
|
||||
video: {
|
||||
bytesReceived: number,
|
||||
packetsReceived: number,
|
||||
packetsLost: number,
|
||||
jitter?: number, // Firefox
|
||||
jitterBufferDelay?: number, // Chrome
|
||||
framesDecoded: number,
|
||||
firCount: number,
|
||||
nackCount: number,
|
||||
pliCount: number,
|
||||
frameHeight?: number, // Chrome
|
||||
frameWidth?: number, // Chrome
|
||||
framesDropped?: number, // Chrome
|
||||
framesReceived?: number // Chrome
|
||||
} | {}
|
||||
},
|
||||
outbound?: {
|
||||
audio: {
|
||||
bytesSent: number,
|
||||
packetsSent: number,
|
||||
} | {},
|
||||
video: {
|
||||
bytesSent: number,
|
||||
packetsSent: number,
|
||||
firCount: number,
|
||||
framesEncoded: number,
|
||||
nackCount: number,
|
||||
pliCount: number,
|
||||
qpSum: number,
|
||||
frameHeight?: number, // Chrome
|
||||
frameWidth?: number, // Chrome
|
||||
framesSent?: number // Chrome
|
||||
} | {}
|
||||
},
|
||||
candidatepair?: {
|
||||
currentRoundTripTime?: number // Chrome
|
||||
availableOutgoingBitrate?: number //Chrome
|
||||
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
|
||||
}
|
||||
};
|
||||
|
||||
export class WebRtcStats {
|
||||
|
||||
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
|
||||
|
||||
private webRtcStatsEnabled = false;
|
||||
private webRtcStatsIntervalId: NodeJS.Timer;
|
||||
private statsInterval = 1;
|
||||
private POST_URL: string;
|
||||
|
||||
constructor(private stream: Stream) {
|
||||
platform = PlatformUtils.getInstance();
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return this.webRtcStatsEnabled;
|
||||
}
|
||||
|
||||
public initWebRtcStats(): void {
|
||||
|
||||
const webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
|
||||
|
||||
if (!!webrtcObj) {
|
||||
this.webRtcStatsEnabled = true;
|
||||
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
|
||||
// webrtc object found in local storage
|
||||
logger.warn('WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
|
||||
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
|
||||
|
||||
this.POST_URL = webrtcStatsConfig.httpEndpoint;
|
||||
this.statsInterval = webrtcStatsConfig.interval; // Interval in seconds
|
||||
|
||||
this.webRtcStatsIntervalId = setInterval(async () => {
|
||||
await this.sendStatsToHttpEndpoint();
|
||||
}, this.statsInterval * 1000);
|
||||
|
||||
} else {
|
||||
logger.debug('WebRtc stats not enabled');
|
||||
}
|
||||
}
|
||||
|
||||
// {
|
||||
// "localCandidate": {
|
||||
// "id": "RTCIceCandidate_/r4P1y2Q",
|
||||
// "timestamp": 1616080155617,
|
||||
// "type": "local-candidate",
|
||||
// "transportId": "RTCTransport_0_1",
|
||||
// "isRemote": false,
|
||||
// "networkType": "wifi",
|
||||
// "ip": "123.45.67.89",
|
||||
// "port": 63340,
|
||||
// "protocol": "udp",
|
||||
// "candidateType": "srflx",
|
||||
// "priority": 1686052607,
|
||||
// "deleted": false,
|
||||
// "raw": [
|
||||
// "candidate:3345412921 1 udp 1686052607 123.45.67.89 63340 typ srflx raddr 192.168.1.31 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10",
|
||||
// "candidate:58094482 1 udp 41885695 98.76.54.32 44431 typ relay raddr 123.45.67.89 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10"
|
||||
// ]
|
||||
// },
|
||||
// "remoteCandidate": {
|
||||
// "id": "RTCIceCandidate_1YO18gph",
|
||||
// "timestamp": 1616080155617,
|
||||
// "type": "remote-candidate",
|
||||
// "transportId": "RTCTransport_0_1",
|
||||
// "isRemote": true,
|
||||
// "ip": "12.34.56.78",
|
||||
// "port": 64989,
|
||||
// "protocol": "udp",
|
||||
// "candidateType": "srflx",
|
||||
// "priority": 1679819263,
|
||||
// "deleted": false,
|
||||
// "raw": [
|
||||
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989",
|
||||
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989"
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// Have been tested in:
|
||||
// - Linux Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Opera 74.0.3911.218
|
||||
// - Firefox 86
|
||||
// - Microsoft Edge 91.0.825.0
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - Windows Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Opera 74.0.3911.232
|
||||
// - Firefox 86.0.1
|
||||
// - Microsoft Edge 89.0.774.54
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - MacOS Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Firefox 87.0
|
||||
// - Opera 75.0.3969.93
|
||||
// - Microsoft Edge 89.0.774.57
|
||||
// - Safari 14.0 (14610.1.28.1.9)
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - Android:
|
||||
// - Chrome Mobile 89.0.4389.90
|
||||
// - Opera 62.3.3146.57763
|
||||
// - Firefox Mobile 86.6.1
|
||||
// - Microsoft Edge Mobile 46.02.4.5147
|
||||
// - Ionic 5
|
||||
// - React Native 0.64
|
||||
// - iOS:
|
||||
// - Safari Mobile
|
||||
// - ¿Ionic?
|
||||
// - ¿React Native?
|
||||
public getSelectedIceCandidateInfo(): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
||||
let transportStat;
|
||||
const candidatePairs: Map<string, any> = new Map();
|
||||
const localCandidates: Map<string, any> = new Map();
|
||||
const remoteCandidates: Map<string, any> = new Map();
|
||||
statsReport.forEach((stat: any) => {
|
||||
if (stat.type === 'transport' && (platform.isChromium() || platform.isSafariBrowser() || platform.isReactNative())) {
|
||||
transportStat = stat;
|
||||
}
|
||||
switch (stat.type) {
|
||||
case 'candidate-pair':
|
||||
candidatePairs.set(stat.id, stat);
|
||||
break;
|
||||
case 'local-candidate':
|
||||
localCandidates.set(stat.id, stat);
|
||||
break;
|
||||
case 'remote-candidate':
|
||||
remoteCandidates.set(stat.id, stat);
|
||||
break;
|
||||
}
|
||||
});
|
||||
let selectedCandidatePair;
|
||||
if (transportStat != null) {
|
||||
const selectedCandidatePairId = transportStat.selectedCandidatePairId
|
||||
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
|
||||
} else {
|
||||
// This is basically Firefox
|
||||
const length = candidatePairs.size;
|
||||
const iterator = candidatePairs.values();
|
||||
for (let i = 0; i < length; i++) {
|
||||
const candidatePair = iterator.next().value;
|
||||
if (candidatePair['selected']) {
|
||||
selectedCandidatePair = candidatePair;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const localCandidateId = selectedCandidatePair.localCandidateId;
|
||||
const remoteCandidateId = selectedCandidatePair.remoteCandidateId;
|
||||
let finalLocalCandidate = localCandidates.get(localCandidateId);
|
||||
if (!!finalLocalCandidate) {
|
||||
const candList = this.stream.getLocalIceCandidateList();
|
||||
const cand = candList.filter((c: RTCIceCandidate) => {
|
||||
return (!!c.candidate &&
|
||||
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
|
||||
c.candidate.indexOf(finalLocalCandidate.port) >= 0);
|
||||
});
|
||||
finalLocalCandidate.raw = [];
|
||||
for (let c of cand) {
|
||||
finalLocalCandidate.raw.push(c.candidate);
|
||||
}
|
||||
} else {
|
||||
finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
|
||||
}
|
||||
|
||||
let finalRemoteCandidate = remoteCandidates.get(remoteCandidateId);
|
||||
if (!!finalRemoteCandidate) {
|
||||
const candList = this.stream.getRemoteIceCandidateList();
|
||||
const cand = candList.filter((c: RTCIceCandidate) => {
|
||||
return (!!c.candidate &&
|
||||
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
|
||||
c.candidate.indexOf(finalRemoteCandidate.port) >= 0);
|
||||
});
|
||||
finalRemoteCandidate.raw = [];
|
||||
for (let c of cand) {
|
||||
finalRemoteCandidate.raw.push(c.candidate);
|
||||
}
|
||||
} else {
|
||||
finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
|
||||
}
|
||||
|
||||
resolve({
|
||||
localCandidate: finalLocalCandidate,
|
||||
remoteCandidate: finalRemoteCandidate
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public stopWebRtcStats() {
|
||||
if (this.webRtcStatsEnabled) {
|
||||
clearInterval(this.webRtcStatsIntervalId);
|
||||
logger.warn('WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendStats(url: string, response: JSONStatsResponse): Promise<void> {
|
||||
try {
|
||||
const configuration: RequestInit = {
|
||||
headers: {
|
||||
'Content-type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(response),
|
||||
method: 'POST',
|
||||
};
|
||||
await fetch(url, configuration);
|
||||
|
||||
} catch (error) {
|
||||
logger.error(`sendStats error: ${JSON.stringify(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendStatsToHttpEndpoint(): Promise<void> {
|
||||
try {
|
||||
const webrtcStats: IWebrtcStats = await this.getCommonStats();
|
||||
const response = this.generateJSONStatsResponse(webrtcStats);
|
||||
await this.sendStats(this.POST_URL, response);
|
||||
} catch (error) {
|
||||
logger.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
// Have been tested in:
|
||||
// - Linux Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Opera 74.0.3911.218
|
||||
// - Firefox 86
|
||||
// - Microsoft Edge 91.0.825.0
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - Windows Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Opera 74.0.3911.232
|
||||
// - Firefox 86.0.1
|
||||
// - Microsoft Edge 89.0.774.54
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - MacOS Desktop:
|
||||
// - Chrome 89.0.4389.90
|
||||
// - Opera 75.0.3969.93
|
||||
// - Firefox 87.0
|
||||
// - Microsoft Edge 89.0.774.57
|
||||
// - Safari 14.0 (14610.1.28.1.9)
|
||||
// - Electron 11.3.0 (Chromium 87.0.4280.141)
|
||||
// - Android:
|
||||
// - Chrome Mobile 89.0.4389.90
|
||||
// - Opera 62.3.3146.57763
|
||||
// - Firefox Mobile 86.6.1
|
||||
// - Microsoft Edge Mobile 46.02.4.5147
|
||||
// - Ionic 5
|
||||
// - React Native 0.64
|
||||
// - iOS:
|
||||
// - Safari Mobile
|
||||
// - ¿Ionic?
|
||||
// - ¿React Native?
|
||||
public async getCommonStats(): Promise<IWebrtcStats> {
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
try {
|
||||
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
|
||||
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
|
||||
const videoTrackStats = ['framesReceived', 'framesDropped', 'framesSent', 'frameHeight', 'frameWidth'];
|
||||
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
|
||||
|
||||
statsReport.forEach((stat: any) => {
|
||||
|
||||
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
|
||||
const addStat = (direction: string, key: string): void => {
|
||||
if (stat[key] != null && response[direction] != null) {
|
||||
if (!mediaType && (videoTrackStats.indexOf(key) > -1)) {
|
||||
mediaType = 'video';
|
||||
}
|
||||
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
|
||||
response[direction][mediaType][key] = Number(stat[key]);
|
||||
} else if(direction != null && key != null && candidatePairStats.includes(key)) {
|
||||
// candidate-pair-stats
|
||||
response[direction][key] = Number(stat[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (stat.type) {
|
||||
case "outbound-rtp":
|
||||
addStat('outbound', 'bytesSent');
|
||||
addStat('outbound', 'packetsSent');
|
||||
addStat('outbound', 'framesEncoded');
|
||||
addStat('outbound', 'nackCount');
|
||||
addStat('outbound', 'firCount');
|
||||
addStat('outbound', 'pliCount');
|
||||
addStat('outbound', 'qpSum');
|
||||
break;
|
||||
case "inbound-rtp":
|
||||
addStat('inbound', 'bytesReceived');
|
||||
addStat('inbound', 'packetsReceived');
|
||||
addStat('inbound', 'packetsLost');
|
||||
addStat('inbound', 'jitter');
|
||||
addStat('inbound', 'framesDecoded');
|
||||
addStat('inbound', 'nackCount');
|
||||
addStat('inbound', 'firCount');
|
||||
addStat('inbound', 'pliCount');
|
||||
break;
|
||||
case 'track':
|
||||
addStat('inbound', 'jitterBufferDelay');
|
||||
addStat('inbound', 'framesReceived');
|
||||
addStat('outbound', 'framesDropped');
|
||||
addStat('outbound', 'framesSent');
|
||||
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameHeight');
|
||||
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameWidth');
|
||||
break;
|
||||
case 'candidate-pair':
|
||||
addStat('candidatepair', 'currentRoundTripTime');
|
||||
addStat('candidatepair', 'availableOutgoingBitrate');
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Delete candidatepair from response if null
|
||||
if(!response?.candidatepair || Object.keys(<Object>response.candidatepair).length === 0){
|
||||
delete response.candidatepair;
|
||||
}
|
||||
|
||||
return resolve(response);
|
||||
} catch (error) {
|
||||
logger.error('Error getting common stats: ', error);
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private generateJSONStatsResponse(stats: IWebrtcStats): JSONStatsResponse {
|
||||
return {
|
||||
'@timestamp': new Date().toISOString(),
|
||||
participant_id: this.stream.connection.data,
|
||||
session_id: this.stream.session.sessionId,
|
||||
platform: platform.getName(),
|
||||
platform_description: platform.getDescription(),
|
||||
stream: 'webRTC',
|
||||
webrtc_stats: stats
|
||||
};
|
||||
}
|
||||
|
||||
private getWebRtcStatsResponseOutline(): IWebrtcStats {
|
||||
if (this.stream.isLocal()) {
|
||||
return {
|
||||
outbound: {
|
||||
audio: {},
|
||||
video: {}
|
||||
},
|
||||
candidatepair: {}
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
inbound: {
|
||||
audio: {},
|
||||
video: {}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import { JL } from 'jsnlog';
|
||||
|
||||
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 { ConnectionPropertyChangedEvent } from './OpenViduInternal/Events/ConnectionPropertyChangedEvent';
|
||||
export { FilterEvent } from './OpenViduInternal/Events/FilterEvent';
|
||||
export { NetworkQualityLevelChangedEvent } from './OpenViduInternal/Events/NetworkQualityLevelChangedEvent';
|
||||
export { ExceptionEvent } from './OpenViduInternal/Events/ExceptionEvent';
|
||||
|
||||
export { Capabilities } from './OpenViduInternal/Interfaces/Public/Capabilities';
|
||||
export { Device } from './OpenViduInternal/Interfaces/Public/Device';
|
||||
export { EventDispatcher } from './OpenVidu/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';
|
||||
|
||||
// Disable jsnlog when library is loaded
|
||||
JL.setOptions({ enabled: false })
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/target/
|
|
@ -0,0 +1,13 @@
|
|||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://docs.openvidu.io/en/stable/?badge=stable)
|
||||
[](https://hub.docker.com/r/openvidu/)
|
||||
[](https://openvidu.discourse.group/)
|
||||
|
||||
[![][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
|
|
@ -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>
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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_ALREADY_STREAMING_ERROR_CODE(106), 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),
|
||||
|
||||
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),
|
||||
|
||||
FORCED_CODEC_NOT_FOUND_IN_SDPOFFER(800),
|
||||
|
||||
MEDIA_NODE_NOT_FOUND(900), MEDIA_NODE_STATUS_WRONG(901);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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 PREPARERECEIVEVIDEO_METHOD = "prepareReceiveVideoFrom";
|
||||
public static final String PREPARERECEIVEVIDEO_SDPOFFER_PARAM = "sdpOffer";
|
||||
public static final String PREPARERECEIVEVIDEO_RECONNECT_PARAM = "reconnect";
|
||||
|
||||
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 CONNECTIONPERTYCHANGED_METHOD = "connectionPropertyChanged";
|
||||
public static final String CONNECTIONROPERTYCHANGED_PROPERTY_PARAM = "property";
|
||||
public static final String CONNECTIONPROPERTYCHANGED_NEWVALUE_PARAM = "newValue";
|
||||
|
||||
public static final String NETWORKQUALITYLEVELCHANGED_METHOD = "networkQualityLevelChanged";
|
||||
public static final String NETWORKQUALITYCHANGED_CONNECTIONID_PARAM = "connectionId";
|
||||
public static final String NETWORKQUALITYCHANGED_NEWVALUE_PARAM = "newValue";
|
||||
public static final String NETWORKQUALITYCHANGED_OLDVALUE_PARAM = "oldValue";
|
||||
|
||||
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";
|
||||
|
||||
public static final String RECONNECTSTREAM_METHOD = "reconnectStream";
|
||||
public static final String RECONNECTSTREAM_STREAM_PARAM = "stream";
|
||||
public static final String RECONNECTSTREAM_SDPSTRING_PARAM = "sdpString";
|
||||
public static final String RECONNECTSTREAM_FORCIBLYRECONNECT_PARAM = "forciblyReconnect";
|
||||
// TODO: REMOVE ON 2.18.0
|
||||
public static final String RECONNECTSTREAM_SDPOFFER_PARAM = "sdpOffer";
|
||||
// ENDTODO
|
||||
|
||||
public static final String VIDEODATA_METHOD = "videoData";
|
||||
|
||||
public static final String ECHO_METHOD = "echo";
|
||||
|
||||
public static final String FORCIBLYRECONNECTSUBSCRIBER_METHOD = "forciblyReconnectSubscriber";
|
||||
public static final String FORCIBLYRECONNECTSUBSCRIBER_CONNECTIONID_PARAM = "connectionId";
|
||||
public static final String FORCIBLYRECONNECTSUBSCRIBER_STREAMID_PARAM = "streamId";
|
||||
public static final String FORCIBLYRECONNECTSUBSCRIBER_SDPOFFER_PARAM = "sdpOffer";
|
||||
|
||||
// ---------------------------- SERVER RESPONSES & EVENTS -----------------
|
||||
|
||||
public static final String PARTICIPANTJOINED_METHOD = "participantJoined";
|
||||
public static final String PARTICIPANTJOINED_USER_PARAM = "id";
|
||||
public static final String PARTICIPANTJOINED_FINALUSERID_PARAM = "finalUserId";
|
||||
public static final String PARTICIPANTJOINED_CREATEDAT_PARAM = "createdAt";
|
||||
public static final String PARTICIPANTJOINED_METADATA_PARAM = "metadata";
|
||||
public static final String PARTICIPANTJOINED_VALUE_PARAM = "value";
|
||||
public static final String PARTICIPANTJOINED_SESSION_PARAM = "session";
|
||||
public static final String PARTICIPANTJOINED_VERSION_PARAM = "version";
|
||||
public static final String PARTICIPANTJOINED_MEDIASERVER_PARAM = "mediaServer";
|
||||
public static final String PARTICIPANTJOINED_RECORD_PARAM = "record";
|
||||
public static final String PARTICIPANTJOINED_ROLE_PARAM = "role";
|
||||
public static final String PARTICIPANTJOINED_COTURNIP_PARAM = "coturnIp";
|
||||
public static final String PARTICIPANTJOINED_TURNUSERNAME_PARAM = "turnUsername";
|
||||
public static final String PARTICIPANTJOINED_TURNCREDENTIAL_PARAM = "turnCredential";
|
||||
|
||||
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";
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2020 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));
|
||||
|
||||
}
|
||||
}
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
**/node_modules/
|
||||
**/.angular/
|
||||
**/.vscode
|
||||
|
||||
|
||||
node_modules
|
||||
dist/
|
||||
docs/
|
||||
coverage/**
|
|
@ -1,5 +0,0 @@
|
|||
build
|
||||
node_modules
|
||||
.github
|
||||
dist
|
||||
docs
|
|
@ -1,10 +0,0 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 140,
|
||||
"trailingComma": "none",
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"useTabs": true,
|
||||
"jsxSingleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue