mirror of https://github.com/OpenVidu/openvidu.git
Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
9a2803cb06 | |
![]() |
4af3f75f1e | |
![]() |
c2b392d6d8 | |
![]() |
aac968eaba |
|
@ -0,0 +1,109 @@
|
|||
name: OpenVidu CE Tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths-ignore:
|
||||
- ".github/workflows/openvidu-components-angular-E2E.yml"
|
||||
- "openvidu-components-angular/**"
|
||||
- "openvidu-server/docker/**"
|
||||
- "openvidu-server/deployments/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
TEST_IMAGE:
|
||||
description: "Docker image where to run the tests"
|
||||
required: true
|
||||
default: "openvidu/openvidu-test-e2e:22.04"
|
||||
KURENTO_JAVA_COMMIT:
|
||||
description: 'Commit to use in kurento-java dependencies. If "default" the release version declared in property "version.kurento" of openvidu-parent/pom.xml will be used'
|
||||
required: true
|
||||
default: "default"
|
||||
KURENTO_MEDIA_SERVER_IMAGE:
|
||||
description: "Docker image of kurento-media-server"
|
||||
required: true
|
||||
default: "kurento/kurento-media-server:6.18.0"
|
||||
DOCKER_RECORDING_VERSION:
|
||||
description: "Force version of openvidu/openvidu-recording container"
|
||||
required: true
|
||||
default: "default"
|
||||
CHROME_VERSION:
|
||||
description: "Version of Chrome to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-chrome/tags"
|
||||
required: true
|
||||
default: "latest"
|
||||
FIREFOX_VERSION:
|
||||
description: "Version of Firefox to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-firefox/tags"
|
||||
required: true
|
||||
default: "latest"
|
||||
EDGE_VERSION:
|
||||
description: "Version of Edge to use. Must be a valid image tag from https://hub.docker.com/r/selenium/standalone-edge/tags"
|
||||
required: true
|
||||
default: "latest"
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ github.event.inputs.TEST_IMAGE || 'openvidu/openvidu-test-e2e:22.04' }}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- /opt/openvidu:/opt/openvidu
|
||||
env:
|
||||
TEST_IMAGE: ${{ github.event.inputs.TEST_IMAGE || 'openvidu/openvidu-test-e2e:22.04' }}
|
||||
KURENTO_SNAPSHOTS_URL: ${{ secrets.KURENTO_SNAPSHOTS_URL }}
|
||||
KURENTO_MEDIA_SERVER_IMAGE: ${{ github.event.inputs.KURENTO_MEDIA_SERVER_IMAGE || 'kurento/kurento-media-server:6.18.0' }}
|
||||
KURENTO_JAVA_COMMIT: ${{ github.event.inputs.KURENTO_JAVA_COMMIT || 'default' }}
|
||||
DOCKER_RECORDING_VERSION: ${{ github.event.inputs.DOCKER_RECORDING_VERSION || 'default' }}
|
||||
CHROME_VERSION: ${{ github.event.inputs.CHROME_VERSION || 'latest' }}
|
||||
FIREFOX_VERSION: ${{ github.event.inputs.FIREFOX_VERSION || 'latest' }}
|
||||
EDGE_VERSION: ${{ github.event.inputs.EDGE_VERSION || 'latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup scripts
|
||||
run: |
|
||||
curl -s https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/commons.sh -o /opt/commons.sh && chmod u+x /opt/commons.sh
|
||||
cp ci-scripts/openvidu-e2e-tests.sh /opt/openvidu-e2e-tests.sh && chmod u+x /opt/openvidu-e2e-tests.sh
|
||||
- name: Clean environment
|
||||
run: /opt/commons.sh --clean-environment
|
||||
- name: Prepare test environment
|
||||
run: /opt/commons.sh --prepare-test-environment "${TEST_IMAGE}"
|
||||
- name: Use specific kurento-java commit
|
||||
if: ${{ env.KURENTO_JAVA_COMMIT != 'default'}}
|
||||
run: /opt/commons.sh --use-specific-kurento-java-commit
|
||||
- name: Build openvidu-browser
|
||||
run: /opt/commons.sh --build-openvidu-browser
|
||||
- name: Build openvidu-node-client
|
||||
run: /opt/commons.sh --build-openvidu-node-client
|
||||
- name: Build openvidu-java-client
|
||||
run: /opt/commons.sh --build-openvidu-java-client
|
||||
- name: Build openvidu-parent
|
||||
run: /opt/commons.sh --build-openvidu-parent
|
||||
- name: Build openvidu-testapp
|
||||
run: /opt/commons.sh --build-openvidu-testapp
|
||||
- name: Build openvidu-server dashboard
|
||||
run: /opt/commons.sh --build-openvidu-server-dashboard true
|
||||
- name: Build openvidu-server
|
||||
run: /opt/commons.sh --build-openvidu-server
|
||||
- name: openvidu-server unit tests
|
||||
run: /opt/openvidu-e2e-tests.sh --openvidu-server-unit-tests
|
||||
- name: openvidu-server integration tests
|
||||
run: /opt/openvidu-e2e-tests.sh --openvidu-server-integration-tests
|
||||
- name: Environment launch Kurento
|
||||
run: /opt/openvidu-e2e-tests.sh --environment-launch-kurento
|
||||
- name: Serve openvidu-testapp
|
||||
run: /opt/commons.sh --serve-openvidu-testapp
|
||||
- name: OpenVidu E2E Tests Kurento
|
||||
run: /opt/openvidu-e2e-tests.sh --openvidu-e2e-tests-kurento
|
||||
- name: Test reports
|
||||
uses: mikepenz/action-junit-report@v3
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
report_paths: "**/target/surefire-reports/TEST-*.xml"
|
||||
- name: Upload logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always() # always run even if the previous step fails
|
||||
with:
|
||||
name: Logs
|
||||
path: |
|
||||
/opt/openvidu/*.log
|
|
@ -0,0 +1,158 @@
|
|||
name: openvidu-components-angular E2E
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'openvidu-components-angular/**'
|
||||
- 'openvidu-browser/**'
|
||||
- 'openvidu-node-client/**'
|
||||
- '.github/workflows/openvidu-components-angular-E2E.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:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha || github.sha }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- name: Commit URL
|
||||
run: echo https://github.com/OpenVidu/openvidu/commit/${{ github.event.inputs.commit_sha || github.sha }}
|
||||
- name: Send repository 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"'"}}'
|
||||
- name: Build openvidu-browser
|
||||
run: |
|
||||
cd openvidu-browser
|
||||
npm install
|
||||
npm run build && \
|
||||
npm pack
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: openvidu-browser
|
||||
path: openvidu-browser/openvidu-browser-*.tgz
|
||||
|
||||
|
||||
openvidu_angular_e2e:
|
||||
needs: test_setup
|
||||
name: OpenVidu Angular E2E tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha || github.sha }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- name: Run Browserless Chrome
|
||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
||||
- name: Run openvidu-server-kms
|
||||
run: |
|
||||
docker run -p 4443:4443 --rm -d \
|
||||
-e OPENVIDU_SECRET=MY_SECRET \
|
||||
openvidu/openvidu-dev:latest
|
||||
- name: Install openvidu-browser and dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install openvidu-browser-*.tgz
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-angular-testapp
|
||||
run: npm run build --prefix openvidu-components-angular
|
||||
- name: Serve openvidu-angular-testapp
|
||||
run: npm run start-prod --prefix openvidu-components-angular &
|
||||
- name: Run openvidu-angular E2E
|
||||
run: npm run lib:e2e-ci --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e:
|
||||
needs: test_setup
|
||||
name: Webcomponent E2E CE tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ github.event.inputs.commit_sha || github.sha }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- name: Run Browserless Chrome
|
||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
||||
- name: Run openvidu-server-kms
|
||||
run: |
|
||||
docker run -p 4443:4443 --rm -d \
|
||||
-e OPENVIDU_SECRET=MY_SECRET \
|
||||
openvidu/openvidu-dev:latest
|
||||
- name: Install openvidu-browser and dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install openvidu-browser-*.tgz
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Run Webcomponent E2E
|
||||
run: npm run webcomponent:e2e-ci --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_pro:
|
||||
if: false #Skip PRO test because infra is unstable
|
||||
needs: test_setup
|
||||
name: Webcomponent E2E PRO tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: openvidu-browser
|
||||
path: openvidu-components-angular
|
||||
- name: Run Browserless Chrome
|
||||
run: docker run -d -p 3000:3000 --network host browserless/chrome:1.53-chrome-stable
|
||||
- name: Install openvidu-browser and dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install openvidu-browser-*.tgz
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Run Webcomponent E2E PRO
|
||||
env:
|
||||
OPENVIDU_SERVER_URL: ${{ secrets.OPENVIDU_CALL_NEXT_URL }}
|
||||
OPENVIDU_SECRET: ${{ secrets.OPENVIDU_CALL_NEXT_SECRET }}
|
||||
run: npm run webcomponent:e2e-pro-ci --prefix openvidu-components-angular
|
|
@ -1,478 +0,0 @@
|
|||
name: openvidu-components-angular Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
paths:
|
||||
- 'openvidu-components-angular/**'
|
||||
- '.github/workflows/openvidu-components-angular-tests.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit_sha:
|
||||
description: 'Commit SHA'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
test_setup:
|
||||
name: Test setup
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Commit URL
|
||||
run: echo https://github.com/OpenVidu/openvidu/commit/${{ inputs.commit_sha || github.sha }}
|
||||
- name: Send Dispatch Event
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.OPENVIDU_DISPATCH_EVENT_GA }}
|
||||
COMMIT_MESSAGE: ${{ github.event.head_commit.message || 'Manually' }}
|
||||
COMMIT_URL: ${{ github.event.commits[0].url || 'Manually' }}
|
||||
BRANCH_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
curl -X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
||||
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \
|
||||
-d '{"event_type":"openvidu-components-angular","client_payload":{"commit-message":"'"$COMMIT_MESSAGE"'","commit-ref":"'"$COMMIT_URL"'", "branch-name":"'"$BRANCH_NAME"'"}}'
|
||||
|
||||
nested_events:
|
||||
needs: test_setup
|
||||
name: Nested events
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run nested components E2E event tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:nested-events --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
nested_structural_directives:
|
||||
needs: test_setup
|
||||
name: Nested Structural Directives
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run nested structural directives tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:nested-structural-directives --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
nested_attribute_directives:
|
||||
needs: test_setup
|
||||
name: Nested Attribute Directives
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run nested attribute directives tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:nested-attribute-directives --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_directives:
|
||||
needs: test_setup
|
||||
name: API Directives Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-directives --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
e2e_internal_directives:
|
||||
needs: test_setup
|
||||
name: Internal Directives Tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-internal-directives --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_chat:
|
||||
needs: test_setup
|
||||
name: Chat E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-chat --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_events:
|
||||
needs: test_setup
|
||||
name: Events E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-events --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_media_devices:
|
||||
needs: test_setup
|
||||
name: Media devices E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-media-devices --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_panels:
|
||||
needs: test_setup
|
||||
name: Panels E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-panels --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_screen_sharing:
|
||||
needs: test_setup
|
||||
name: Screen sharing E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-screensharing --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_stream:
|
||||
needs: test_setup
|
||||
name: Stream E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -v $(pwd)/openvidu-components-angular/e2e/assets:/e2e-assets selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Tests
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-stream --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_toolbar:
|
||||
needs: test_setup
|
||||
name: Toolbar E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-toolbar --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
|
||||
e2e_virtual_backgrounds:
|
||||
needs: test_setup
|
||||
name: Virtual Backgrounds E2E
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit_sha || github.sha }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install wait-on package
|
||||
run: npm install -g wait-on
|
||||
# - name: Run Browserless Chrome
|
||||
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
|
||||
- name: Run Chrome
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
|
||||
- name: Run openvidu-local-deployment
|
||||
uses: OpenVidu/actions/start-openvidu-local-deployment@main
|
||||
- name: Start OpenVidu Call backend
|
||||
uses: OpenVidu/actions/start-openvidu-call@main
|
||||
- name: Build and Serve openvidu-components-angular Testapp
|
||||
uses: OpenVidu/actions/start-openvidu-components-testapp@main
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-virtual-backgrounds --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
|
@ -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
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
[submodule "openvidu-livekit"]
|
||||
path = openvidu-livekit
|
||||
url = https://github.com/OpenVidu/openvidu-livekit.git
|
|
@ -35,11 +35,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,619 @@
|
|||
#!/bin/bash -x
|
||||
set -eu -o pipefail
|
||||
|
||||
################################################################
|
||||
# Any function offered by this file assume that the path is #
|
||||
# located where the first command of each function requires it #
|
||||
################################################################
|
||||
|
||||
# CI flags
|
||||
CLEAN_ENVIRONMENT=false
|
||||
PREPARE_TEST_ENVIRONMENT=false
|
||||
USE_SPECIFIC_KURENTO_JAVA_COMMIT=false
|
||||
SERVE_OV_TESTAPP=false
|
||||
|
||||
# Build artifacts
|
||||
BUILD_OV_BROWSER=false
|
||||
BUILD_OV_NODE_CLIENT=false
|
||||
BUILD_OV_JAVA_CLIENT=false
|
||||
BUILD_OV_PARENT=false
|
||||
BUILD_OV_TESTAPP=false
|
||||
BUILD_OV_SERVER_DASHBOARD=false
|
||||
BUILD_OV_SERVER=false
|
||||
BUILD_OV_SERVER_DEPENDENCY=false
|
||||
BUILD_OV_SERVER_PRO_INSPECTOR=false
|
||||
BUILD_OV_SERVER_PRO=false
|
||||
|
||||
# Bump versions
|
||||
BUMP_NPM_PROJECT_VERSION=false
|
||||
BUMP_NPM_DEPENDENCY_VERSION=false
|
||||
BUMP_MAVEN_PROJECT_VERSION=false
|
||||
BUMP_MAVEN_PROPERTY_VERSION=false
|
||||
BUMP_DOCKER_COMPOSE_SERVICE_VERSION=false
|
||||
BUMP_DOCKER_COMPOSE_HEADER_VERSION=false
|
||||
BUMP_DOCKER_IMAGE_VERSION_IN_FILES=false
|
||||
BUMP_APPLICATION_PROPERTIES_VAR_VALUE=false
|
||||
|
||||
WAIT_FOR_NPM_DEPENDENCY=false
|
||||
|
||||
# Environment variables
|
||||
if [[ -n ${1:-} ]]; then
|
||||
case "${1:-}" in
|
||||
|
||||
--clean-environment)
|
||||
CLEAN_ENVIRONMENT=true
|
||||
;;
|
||||
|
||||
--prepare-test-environment)
|
||||
PREPARE_TEST_ENVIRONMENT=true
|
||||
;;
|
||||
|
||||
--use-specific-kurento-java-commit)
|
||||
USE_SPECIFIC_KURENTO_JAVA_COMMIT=true
|
||||
;;
|
||||
|
||||
--build-openvidu-browser)
|
||||
BUILD_OV_BROWSER=true
|
||||
;;
|
||||
|
||||
--build-openvidu-node-client)
|
||||
BUILD_OV_NODE_CLIENT=true
|
||||
;;
|
||||
|
||||
--build-openvidu-java-client)
|
||||
BUILD_OV_JAVA_CLIENT=true
|
||||
;;
|
||||
|
||||
--build-openvidu-parent)
|
||||
BUILD_OV_PARENT=true
|
||||
;;
|
||||
|
||||
--build-openvidu-testapp)
|
||||
BUILD_OV_TESTAPP=true
|
||||
;;
|
||||
|
||||
--build-openvidu-server-dashboard)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide LINK_LOCAL_DEPENDENCIES as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUILD_OV_SERVER_DASHBOARD=true
|
||||
LINK_LOCAL_DEPENDENCIES="${2}"
|
||||
;;
|
||||
|
||||
--build-openvidu-server)
|
||||
BUILD_OV_SERVER=true
|
||||
;;
|
||||
|
||||
--build-openvidu-server-dependency)
|
||||
BUILD_OV_SERVER_DEPENDENCY=true
|
||||
;;
|
||||
|
||||
--build-openvidu-server-pro-inspector)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide LINK_LOCAL_DEPENDENCIES as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUILD_OV_SERVER_PRO_INSPECTOR=true
|
||||
LINK_LOCAL_DEPENDENCIES="${2}"
|
||||
;;
|
||||
|
||||
--build-openvidu-server-pro)
|
||||
BUILD_OV_SERVER_PRO=true
|
||||
;;
|
||||
|
||||
--serve-openvidu-testapp)
|
||||
SERVE_OV_TESTAPP=true
|
||||
;;
|
||||
|
||||
--bump-npm-project-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide VERSION as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_NPM_PROJECT_VERSION=true
|
||||
VERSION="${2}"
|
||||
;;
|
||||
|
||||
--bump-npm-dependency-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide DEPENDENCY as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide VERSION as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_NPM_DEPENDENCY_VERSION=true
|
||||
DEPENDENCY="${2}"
|
||||
VERSION="${3}"
|
||||
TYPE_OF_DEPENDENCY="${4:-dependencies}" # [dependencies, devDependencies, peerDependencies, optionalDependencies]
|
||||
;;
|
||||
|
||||
--bump-maven-project-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide VERSION as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_MAVEN_PROJECT_VERSION=true
|
||||
VERSION="${2}"
|
||||
;;
|
||||
|
||||
--bump-maven-property-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide PROPERTY as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide VERSION as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_MAVEN_PROPERTY_VERSION=true
|
||||
PROPERTY="${2}"
|
||||
VERSION="${3}"
|
||||
;;
|
||||
|
||||
--bump-docker-compose-service-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide DOCKER_COMPOSE_FILE as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide SERVICE_IMAGE as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide VERSION as 3rd parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_DOCKER_COMPOSE_SERVICE_VERSION=true
|
||||
DOCKER_COMPOSE_FILE="${2}"
|
||||
SERVICE_IMAGE="${3}"
|
||||
VERSION="${4}"
|
||||
;;
|
||||
|
||||
--bump-docker-compose-header-version)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide DOCKER_COMPOSE_FILE as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide HEADER as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide VERSION as 3rd parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_DOCKER_COMPOSE_HEADER_VERSION=true
|
||||
DOCKER_COMPOSE_FILE="${2}"
|
||||
HEADER="${3}"
|
||||
VERSION="${4}"
|
||||
;;
|
||||
|
||||
--bump-docker-image-version-in-files)
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide FILE_NAME_PATTERN as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide IMAGE as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide VERSION as 3rd parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_DOCKER_IMAGE_VERSION_IN_FILES=true
|
||||
FILE_NAME_PATTERN="${2}"
|
||||
IMAGE="${3}"
|
||||
VERSION="${4}"
|
||||
;;
|
||||
|
||||
--bump-application-properties-var-value)
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide APPLICATION_PROPERTIES_FILE as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide VARIABLE as 3rd parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${4:-}" ]]; then
|
||||
echo "Must provide VALUE as 4th parameter"
|
||||
exit 1
|
||||
fi
|
||||
BUMP_APPLICATION_PROPERTIES_VAR_VALUE=true
|
||||
APPLICATION_PROPERTIES_FILE="${2}"
|
||||
VARIABLE="${3}"
|
||||
VALUE="${4}"
|
||||
;;
|
||||
|
||||
--wait-for-npm-dependency)
|
||||
if [[ -z "${2:-}" ]]; then
|
||||
echo "Must provide DEPENDENCY as 1st parameter"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "${3:-}" ]]; then
|
||||
echo "Must provide VERSION as 2nd parameter"
|
||||
exit 1
|
||||
fi
|
||||
WAIT_FOR_NPM_DEPENDENCY=true
|
||||
DEPENDENCY="${2}"
|
||||
VERSION="${3}"
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized method $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "Must provide a method to execute as first parameter when calling the script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
compareFiles() {
|
||||
if cmp -s "$1" "$1-AUX"; then
|
||||
rm -f $1-AUX
|
||||
echo "Error: no changes has been made to $1"
|
||||
echo "Trying to change \"$2\" to \"$3\""
|
||||
exit 1
|
||||
else
|
||||
cp -f "$1-AUX" "$1"
|
||||
rm -f "$1-AUX"
|
||||
fi
|
||||
}
|
||||
|
||||
# -------------
|
||||
# Clean environment
|
||||
# -------------
|
||||
if [[ "${CLEAN_ENVIRONMENT}" == true ]]; then
|
||||
|
||||
# Remove all running containers except test container and runner container
|
||||
ids=$(docker ps -a -q)
|
||||
for id in $ids; do
|
||||
DOCKER_IMAGE=$(docker inspect --format='{{.Config.Image}}' $id)
|
||||
if [[ "${DOCKER_IMAGE}" != *"$TEST_IMAGE"* ]] &&
|
||||
[[ "${DOCKER_IMAGE}" != *"runner-deployment"* ]]; then
|
||||
echo "Removing container image '$DOCKER_IMAGE' with id '$id'"
|
||||
docker stop $id && docker rm $id
|
||||
fi
|
||||
done
|
||||
|
||||
# Clean /opt/openvidu contents
|
||||
rm -rf /opt/openvidu/*
|
||||
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Prepare build
|
||||
# -------------
|
||||
if [[ "${PREPARE_TEST_ENVIRONMENT}" == true ]]; then
|
||||
|
||||
# Connect e2e test container to network bridge so it is vissible for browser and media server containers
|
||||
if [[ -n "${TEST_IMAGE}" ]]; then
|
||||
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')" || echo "Docker container not found for image ${TEST_IMAGE}"
|
||||
if [[ -n "${E2E_CONTAINER_ID}" ]]; then
|
||||
docker network connect bridge "${E2E_CONTAINER_ID}"
|
||||
else
|
||||
echo "Could not connect test docker container to docker bridge, because no running container was found for image \"${TEST_IMAGE}\""
|
||||
fi
|
||||
else
|
||||
echo "No TEST_IMAGE env var provided. Skipping network bridge connection"
|
||||
fi
|
||||
|
||||
# Prepare directory for OpenVidu recordings
|
||||
sudo mkdir -p /opt/openvidu/recordings && sudo chmod 777 /opt/openvidu/recordings
|
||||
# Prepare directory for OpenVidu Android apps
|
||||
sudo mkdir -p /opt/openvidu/android && sudo chmod 777 /opt/openvidu/android
|
||||
|
||||
# Configure Snapshots repository
|
||||
if [[ -n "${KURENTO_SNAPSHOTS_URL:-}" ]]; then
|
||||
sudo mkdir -p /etc/maven
|
||||
sudo chmod -R 777 /etc/maven
|
||||
pushd /etc/maven
|
||||
rm -f settings.xml
|
||||
curl https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/kurento-snapshots.xml -o settings.xml
|
||||
sed -i "s|KURENTO_SNAPSHOTS_URL|${KURENTO_SNAPSHOTS_URL}|g" settings.xml
|
||||
popd
|
||||
fi
|
||||
|
||||
# Download fake videos
|
||||
FAKE_VIDEO1=/opt/openvidu/barcode.y4m
|
||||
FAKE_VIDEO2=/opt/openvidu/girl.mjpeg
|
||||
if [ ! -f ${FAKE_VIDEO1} ]; then
|
||||
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/barcode.y4m --create-dirs --output /opt/openvidu/barcode.y4m
|
||||
else
|
||||
echo "File ${FAKE_VIDEO1} already exists"
|
||||
fi
|
||||
if [ ! -f ${FAKE_VIDEO2} ]; then
|
||||
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/girl.mjpeg --create-dirs --output /opt/openvidu/girl.mjpeg
|
||||
else
|
||||
echo "File ${FAKE_VIDEO2} already exists"
|
||||
fi
|
||||
|
||||
# Download fake audios
|
||||
FAKE_AUDIO1=/opt/openvidu/fakeaudio.wav
|
||||
FAKE_AUDIO2=/opt/openvidu/stt-test.wav
|
||||
if [ ! -f ${FAKE_AUDIO1} ]; then
|
||||
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/fakeaudio.wav --create-dirs --output /opt/openvidu/fakeaudio.wav
|
||||
else
|
||||
echo "File ${FAKE_AUDIO1} already exists"
|
||||
fi
|
||||
if [ ! -f ${FAKE_AUDIO2} ]; then
|
||||
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/stt-test.wav --create-dirs --output /opt/openvidu/stt-test.wav
|
||||
else
|
||||
echo "File ${FAKE_AUDIO2} already exists"
|
||||
fi
|
||||
|
||||
# Download recording custom layout
|
||||
sudo curl --location https://raw.githubusercontent.com/OpenVidu/openvidu/master/openvidu-test-e2e/docker/my-custom-layout/index.html --create-dirs --output /opt/openvidu/test-layouts/layout1/index.html
|
||||
|
||||
# Open permissions for /opt/openvidu folder
|
||||
sudo chmod -R 777 /opt/openvidu
|
||||
|
||||
# Pull browser images
|
||||
# Pull chrome image if env variable CHROME_VERSION is set
|
||||
if [[ -n "${CHROME_VERSION:-}" ]]; then
|
||||
docker pull selenium/standalone-chrome:"${CHROME_VERSION}"
|
||||
fi
|
||||
# Pull firefox image if env variable FIREFOX_VERSION is set
|
||||
if [[ -n "${FIREFOX_VERSION:-}" ]]; then
|
||||
docker pull selenium/standalone-firefox:"${FIREFOX_VERSION}"
|
||||
fi
|
||||
# Pull edge image if env variable EDGE_VERSION is set
|
||||
if [[ -n "${EDGE_VERSION:-}" ]]; then
|
||||
docker pull selenium/standalone-edge:"${EDGE_VERSION}"
|
||||
fi
|
||||
# Pull Docker Android image if env variable DOCKER_ANDROID_IMAGE is set
|
||||
if [[ -n "${DOCKER_ANDROID_IMAGE:-}" ]]; then
|
||||
docker pull "${DOCKER_ANDROID_IMAGE}"
|
||||
fi
|
||||
|
||||
# Pull mediasoup and kurento
|
||||
if [[ -n "${MEDIASOUP_CONTROLLER_VERSION:-}" ]]; then
|
||||
docker pull openvidu/mediasoup-controller:"${MEDIASOUP_CONTROLLER_VERSION}"
|
||||
fi
|
||||
if [[ -n "${KURENTO_MEDIA_SERVER_IMAGE:-}" ]]; then
|
||||
docker pull "${KURENTO_MEDIA_SERVER_IMAGE}"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Use a specific kurento-java commit other than the configured in openvidu-parent pom.xml
|
||||
# -------------
|
||||
if [[ "${USE_SPECIFIC_KURENTO_JAVA_COMMIT}" == true ]]; then
|
||||
|
||||
git clone https://github.com/Kurento/kurento-java.git
|
||||
pushd kurento-java
|
||||
git checkout -f "${KURENTO_JAVA_COMMIT}"
|
||||
MVN_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
|
||||
mvn -B -Dmaven.artifact.threads=1 clean install
|
||||
popd
|
||||
rm -rf kurento-java
|
||||
mvn -B versions:set-property \
|
||||
-Dproperty=version.kurento \
|
||||
-DnewVersion="${MVN_VERSION}"
|
||||
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-browser
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_BROWSER}" == true ]]; then
|
||||
pushd openvidu-browser || exit 1
|
||||
npm install
|
||||
npm run build
|
||||
npm link
|
||||
npm pack
|
||||
mv openvidu-browser-*.tgz /opt/openvidu
|
||||
npm run browserify
|
||||
npm run browserify-prod
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-node-client
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_NODE_CLIENT}" == true ]]; then
|
||||
pushd openvidu-node-client
|
||||
npm install
|
||||
npm run build
|
||||
npm link
|
||||
npm pack
|
||||
mv openvidu-node-client-*.tgz /opt/openvidu
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-java-client
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_JAVA_CLIENT}" == true ]]; then
|
||||
pushd openvidu-java-client
|
||||
MVN_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
|
||||
mvn -B clean compile package
|
||||
mvn -B install:install-file -Dfile=target/openvidu-java-client-${MVN_VERSION}.jar \
|
||||
-DgroupId=io.openvidu \
|
||||
-DartifactId=openvidu-java-client \
|
||||
-Dversion=${MVN_VERSION} -Dpackaging=jar
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-parent
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_PARENT}" == true ]]; then
|
||||
mvn -B -DskipTests=true -Dmaven.artifact.threads=1 clean install
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-testapp
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_TESTAPP}" == true ]]; then
|
||||
pushd openvidu-testapp
|
||||
npm install
|
||||
npm link openvidu-browser openvidu-node-client
|
||||
export NG_CLI_ANALYTICS="false" && ./node_modules/@angular/cli/bin/ng.js build --configuration production --output-path=/opt/openvidu/testapp
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-server dashboard
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_SERVER_DASHBOARD}" == true ]]; then
|
||||
pushd openvidu-server/src/dashboard
|
||||
npm install
|
||||
if [[ "${LINK_LOCAL_DEPENDENCIES}" == true ]]; then
|
||||
npm link openvidu-browser openvidu-node-client
|
||||
fi
|
||||
npm run build-prod
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-server
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_SERVER}" == true ]]; then
|
||||
pushd openvidu-server
|
||||
mvn -B -DskipTests=true clean package
|
||||
mv target/openvidu-server-*.jar /opt/openvidu
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-server dependency
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_SERVER_DEPENDENCY}" == true ]]; then
|
||||
pushd openvidu-server
|
||||
mvn -B -DskipTests=true -Pdependency clean install
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build Inspector
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_SERVER_PRO_INSPECTOR}" == true ]]; then
|
||||
pushd dashboard
|
||||
npm install
|
||||
if [[ "${LINK_LOCAL_DEPENDENCIES}" == true ]]; then
|
||||
npm link openvidu-browser openvidu-node-client
|
||||
fi
|
||||
npm run build-server-prod
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Build openvidu-server-pro
|
||||
# -------------
|
||||
if [[ "${BUILD_OV_SERVER_PRO}" == true ]]; then
|
||||
pushd openvidu-server-pro
|
||||
mvn -B -DskipTests=true clean package
|
||||
mv target/openvidu-server-pro-*.jar /opt/openvidu
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Serve openvidu-testapp
|
||||
# -------------
|
||||
if [[ "${SERVE_OV_TESTAPP}" == true ]]; then
|
||||
# Generate certificate
|
||||
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 \
|
||||
-subj "/CN=www.mydom.com/O=My Company LTD./C=US" \
|
||||
-keyout /opt/openvidu/testapp/key.pem \
|
||||
-out /opt/openvidu/testapp/cert.pem
|
||||
|
||||
# Serve TestApp
|
||||
pushd /opt/openvidu/testapp
|
||||
http-server -S -p 4200 &>/opt/openvidu/testapp.log &
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump NPM project version
|
||||
# -------------
|
||||
if [[ "${BUMP_NPM_PROJECT_VERSION}" == true ]]; then
|
||||
npm version ${VERSION} --git-tag-version=false --commit-hooks=false
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump NPM project dependency
|
||||
# -------------
|
||||
if [[ "${BUMP_NPM_DEPENDENCY_VERSION}" == true ]]; then
|
||||
jq -j ".${TYPE_OF_DEPENDENCY}.\"${DEPENDENCY}\" = \"${VERSION}\"" package.json >package.json-AUX
|
||||
compareFiles package.json version $VERSION
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump Maven project version
|
||||
# -------------
|
||||
if [[ "${BUMP_MAVEN_PROJECT_VERSION}" == true ]]; then
|
||||
cp pom.xml pom.xml-AUX
|
||||
mvn -DskipTests=true versions:set -DnewVersion="${VERSION}"
|
||||
mv pom.xml changed-pom.xml && mv pom.xml-AUX pom.xml && mv changed-pom.xml pom.xml-AUX
|
||||
compareFiles pom.xml "<version>" $VERSION
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump Maven project property
|
||||
# -------------
|
||||
if [[ "${BUMP_MAVEN_PROPERTY_VERSION}" == true ]]; then
|
||||
|
||||
if [[ -n "${OPENVIDU_MAVEN_GENERIC_SETTINGS:-}" ]]; then
|
||||
# Config file with Kurento Snapshots configuration only
|
||||
mkdir -p /tmp/maven-generic-settings
|
||||
echo "${OPENVIDU_MAVEN_GENERIC_SETTINGS}" >/tmp/maven-generic-settings/settings.xml
|
||||
fi
|
||||
|
||||
mvn --batch-mode \
|
||||
--settings /tmp/maven-generic-settings/settings.xml \
|
||||
-DskipTests=true \
|
||||
versions:set-property \
|
||||
-Dproperty="${PROPERTY}" \
|
||||
-DnewVersion="${VERSION}"
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump docker-compose.yml service version
|
||||
# -------------
|
||||
if [[ "${BUMP_DOCKER_COMPOSE_SERVICE_VERSION}" == true ]]; then
|
||||
sed -r "s|image:\s+${SERVICE_IMAGE}:[[:alnum:]._-]+|image: ${SERVICE_IMAGE}:${VERSION}|g" ${DOCKER_COMPOSE_FILE} >${DOCKER_COMPOSE_FILE}-AUX
|
||||
compareFiles $DOCKER_COMPOSE_FILE $SERVICE_IMAGE $VERSION
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump docker-compose.yml header version
|
||||
# -------------
|
||||
if [[ "${BUMP_DOCKER_COMPOSE_HEADER_VERSION}" == true ]]; then
|
||||
sed -r "s|#\s+${HEADER}:\s+[[:alnum:]._-]+|# ${HEADER}: ${VERSION}|g" ${DOCKER_COMPOSE_FILE} >${DOCKER_COMPOSE_FILE}-AUX
|
||||
compareFiles $DOCKER_COMPOSE_FILE $HEADER $VERSION
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump Docker image version in files
|
||||
# -------------
|
||||
if [[ "${BUMP_DOCKER_IMAGE_VERSION_IN_FILES}" == true ]]; then
|
||||
find . -type f -name ${FILE_NAME_PATTERN} | xargs sed -i -r "s|${IMAGE}:[[:alnum:]._-]+|${IMAGE}:${VERSION}|g"
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Bump application.properties variable value
|
||||
# -------------
|
||||
if [[ "${BUMP_APPLICATION_PROPERTIES_VAR_VALUE}" == true ]]; then
|
||||
sed -r "s%${VARIABLE}((:|=)\s*).*$%${VARIABLE}\1${VALUE}%g" ${APPLICATION_PROPERTIES_FILE} >${APPLICATION_PROPERTIES_FILE}-AUX
|
||||
compareFiles $APPLICATION_PROPERTIES_FILE $VARIABLE $VALUE
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Wait for NPM dependency to be available
|
||||
# -------------
|
||||
if [[ "${WAIT_FOR_NPM_DEPENDENCY}" == true ]]; then
|
||||
CHECK_VERSION_AVAILABILTY="npm show ${DEPENDENCY}@${VERSION} version || echo ''"
|
||||
VERSION_AUX=$(eval "${CHECK_VERSION_AVAILABILTY}")
|
||||
until [[ "${VERSION_AUX}" == "${VERSION}" ]]; do
|
||||
echo "Waiting for ${DEPENDENCY}@${VERSION} to be available in NPM..."
|
||||
sleep 2
|
||||
VERSION_AUX=$(eval "${CHECK_VERSION_AVAILABILTY}")
|
||||
done
|
||||
echo "${DEPENDENCY}@${VERSION} already available in NPM"
|
||||
fi
|
|
@ -0,0 +1,43 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
<pluginGroups></pluginGroups>
|
||||
<proxies></proxies>
|
||||
<servers></servers>
|
||||
<mirrors></mirrors>
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>default</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>kurento-github-public</id>
|
||||
<name>Kurento GitHub Maven packages (public access)</name>
|
||||
<url>KURENTO_SNAPSHOTS_URL</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>kurento-github-public</id>
|
||||
<name>Kurento GitHub Maven packages (public access)</name>
|
||||
<url>KURENTO_SNAPSHOTS_URL</url>
|
||||
<releases>
|
||||
<enabled>false</enabled>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
</profile>
|
||||
</profiles>
|
||||
</settings>
|
|
@ -0,0 +1,236 @@
|
|||
#!/bin/bash -x
|
||||
set -eu -o pipefail
|
||||
|
||||
OV_INTEGRATION_TESTS=false
|
||||
OV_UNIT_TESTS=false
|
||||
OV_E2E_KURENTO=false
|
||||
OV_E2E_MEDIASOUP=false
|
||||
|
||||
LAUNCH_OV_KURENTO=false
|
||||
LAUNCH_OV_MEDIASOUP=false
|
||||
|
||||
function environmentLaunch {
|
||||
local MEDIA_SERVER="$1"
|
||||
|
||||
# Get e2e container id
|
||||
local E2E_CONTAINER_ID
|
||||
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')"
|
||||
|
||||
# Get e2e container IP so services running can be accessed by browser and media server containers
|
||||
local E2E_CONTAINER_IP
|
||||
E2E_CONTAINER_IP="$(docker inspect "$E2E_CONTAINER_ID" | awk '/bridge/,/IPAddress/' | grep IPAddress | cut -d'"' -f4)"
|
||||
|
||||
# Kurento and mediasoup needs to run as network host, so we need Docker host IP.
|
||||
local DOCKER_HOST_IP
|
||||
DOCKER_HOST_IP="$(docker network inspect bridge | grep Subnet | cut -d'"' -f4 | cut -d'/' -f1 | sed 's/.$/1/')"
|
||||
|
||||
if [[ "${MEDIA_SERVER}" == "kurento" ]]; then
|
||||
docker run -e KMS_UID=$(id -u) --network=host --detach=true --volume=/opt/openvidu/recordings:/opt/openvidu/recordings "${KURENTO_MEDIA_SERVER_IMAGE}"
|
||||
while true; do
|
||||
RC="$(curl \
|
||||
--silent \
|
||||
--no-buffer \
|
||||
--write-out '%{http_code}' \
|
||||
--header "Connection: Upgrade" \
|
||||
--header "Upgrade: websocket" \
|
||||
--header "Host: ${DOCKER_HOST_IP}" \
|
||||
--header "Origin: ${DOCKER_HOST_IP}" \
|
||||
"http://${DOCKER_HOST_IP}:8888/kurento" || echo '')"
|
||||
|
||||
if [[ "$RC" == "500" ]]; then
|
||||
break
|
||||
else
|
||||
echo "Waiting for ${MEDIA_SERVER}..."
|
||||
sleep 1
|
||||
fi
|
||||
done
|
||||
elif [[ "${MEDIA_SERVER}" == "mediasoup" ]]; then
|
||||
docker run --network=host --restart=always --detach=true \
|
||||
--env=KMS_MIN_PORT=40000 \
|
||||
--env=KMS_MAX_PORT=65535 \
|
||||
--env=OPENVIDU_PRO_LICENSE="${OPENVIDU_PRO_LICENSE}" \
|
||||
--env=OPENVIDU_PRO_LICENSE_API="${OPENVIDU_PRO_LICENSE_API}" \
|
||||
--env=WEBRTC_LISTENIPS_0_ANNOUNCEDIP="${DOCKER_HOST_IP}" \
|
||||
--env=WEBRTC_LISTENIPS_0_IP="${DOCKER_HOST_IP}" \
|
||||
--volume=/opt/openvidu/recordings:/opt/openvidu/recordings \
|
||||
openvidu/mediasoup-controller:"${MEDIASOUP_CONTROLLER_VERSION}"
|
||||
until $(curl --insecure --output /dev/null --silent http://${DOCKER_HOST_IP}:8888/kurento); do
|
||||
echo "Waiting for ${MEDIA_SERVER}..."
|
||||
sleep 1
|
||||
done
|
||||
else
|
||||
echo "Not valid media server"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "${DOCKER_RECORDING_VERSION}" != "default" ]; then
|
||||
echo "Using custom openvidu-recording tag: ${DOCKER_RECORDING_VERSION}"
|
||||
java -jar -DKMS_URIS="[\"ws://${DOCKER_HOST_IP}:8888/kurento\"]" \
|
||||
-DDOMAIN_OR_PUBLIC_IP="${E2E_CONTAINER_IP}" \
|
||||
-DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true \
|
||||
-DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts \
|
||||
-DOPENVIDU_RECORDING_VERSION="${DOCKER_RECORDING_VERSION}" -DOPENVIDU_WEBHOOK=true \
|
||||
-DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook \
|
||||
/opt/openvidu/openvidu-server-*.jar &>/opt/openvidu/openvidu-server-"${MEDIA_SERVER}".log &
|
||||
else
|
||||
echo "Using default openvidu-recording tag"
|
||||
java -jar -DKMS_URIS="[\"ws://${DOCKER_HOST_IP}:8888/kurento\"]" \
|
||||
-DDOMAIN_OR_PUBLIC_IP="${E2E_CONTAINER_IP}" \
|
||||
-DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true \
|
||||
-DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts -DOPENVIDU_WEBHOOK=true \
|
||||
-DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook \
|
||||
/opt/openvidu/openvidu-server-*.jar &>/opt/openvidu/openvidu-server-"${MEDIA_SERVER}".log &
|
||||
fi
|
||||
until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do
|
||||
echo "Waiting for openvidu-server..."
|
||||
sleep 2
|
||||
done
|
||||
}
|
||||
|
||||
function openviduE2ETests {
|
||||
local MEDIA_SERVER="$1"
|
||||
|
||||
# Get e2e container id
|
||||
local E2E_CONTAINER_ID
|
||||
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')"
|
||||
|
||||
# Get e2e container IP so services running can be accessed by browser and media server containers
|
||||
local E2E_CONTAINER_IP
|
||||
E2E_CONTAINER_IP="$(docker inspect "$E2E_CONTAINER_ID" | awk '/bridge/,/IPAddress/' | grep IPAddress | cut -d'"' -f4)"
|
||||
|
||||
# Kurento and mediasoup needs to run as network host, so we need Docker host IP.
|
||||
local DOCKER_HOST_IP
|
||||
DOCKER_HOST_IP="$(docker network inspect bridge | grep Subnet | cut -d'"' -f4 | cut -d'/' -f1 | sed 's/.$/1/')"
|
||||
|
||||
pushd openvidu-test-e2e
|
||||
if [[ "${MEDIA_SERVER}" == "kurento" ]]; then
|
||||
|
||||
mvn -DMEDIA_SERVER_IMAGE="${KURENTO_MEDIA_SERVER_IMAGE}" \
|
||||
-DOPENVIDU_URL="https://${E2E_CONTAINER_IP}:4443" \
|
||||
-DCHROME_VERSION="${CHROME_VERSION}" \
|
||||
-DFIREFOX_VERSION="${FIREFOX_VERSION}" \
|
||||
-DEDGE_VERSION="${EDGE_VERSION}" \
|
||||
-Dtest=OpenViduTestAppE2eTest \
|
||||
-DAPP_URL="https://${E2E_CONTAINER_IP}:4200" \
|
||||
-DEXTERNAL_CUSTOM_LAYOUT_URL="http://${E2E_CONTAINER_IP}:4114" \
|
||||
-DREMOTE_URL_CHROME="http://${DOCKER_HOST_IP}:6666/wd/hub/" \
|
||||
-DREMOTE_URL_FIREFOX="http://${DOCKER_HOST_IP}:6667/wd/hub/" \
|
||||
-DREMOTE_URL_OPERA="http://${DOCKER_HOST_IP}:6668/wd/hub/" \
|
||||
-DREMOTE_URL_EDGE="http://${DOCKER_HOST_IP}:6669/wd/hub/" \
|
||||
-DEXTERNAL_CUSTOM_LAYOUT_PARAMS="sessionId,CUSTOM_LAYOUT_SESSION,secret,MY_SECRET" \
|
||||
test
|
||||
|
||||
elif [[ "${MEDIA_SERVER}" == "mediasoup" ]]; then
|
||||
|
||||
mvn -DMEDIA_SERVER_IMAGE="openvidu/mediasoup-controller:${MEDIASOUP_CONTROLLER_VERSION}" \
|
||||
-DOPENVIDU_URL="https://${E2E_CONTAINER_IP}:4443" \
|
||||
-DCHROME_VERSION="${CHROME_VERSION}" \
|
||||
-DFIREFOX_VERSION="${FIREFOX_VERSION}" \
|
||||
-DEDGE_VERSION="${EDGE_VERSION}" \
|
||||
-Dtest=OpenViduTestAppE2eTest \
|
||||
-DAPP_URL="https://${E2E_CONTAINER_IP}:4200" \
|
||||
-DEXTERNAL_CUSTOM_LAYOUT_URL="http://${E2E_CONTAINER_IP}:4114" \
|
||||
-DREMOTE_URL_CHROME="http://${DOCKER_HOST_IP}:6666/wd/hub/" \
|
||||
-DREMOTE_URL_FIREFOX="http://${DOCKER_HOST_IP}:6667/wd/hub/" \
|
||||
-DREMOTE_URL_OPERA="http://${DOCKER_HOST_IP}:6668/wd/hub/" \
|
||||
-DREMOTE_URL_EDGE="http://${DOCKER_HOST_IP}:6669/wd/hub/" \
|
||||
-DEXTERNAL_CUSTOM_LAYOUT_PARAMS="sessionId,CUSTOM_LAYOUT_SESSION,secret,MY_SECRET" \
|
||||
-DOPENVIDU_PRO_LICENSE="${OPENVIDU_PRO_LICENSE}" \
|
||||
-DOPENVIDU_PRO_LICENSE_API="${OPENVIDU_PRO_LICENSE_API}" \
|
||||
test
|
||||
|
||||
else
|
||||
echo "Not valid media server"
|
||||
exit 1
|
||||
fi
|
||||
stopMediaServer
|
||||
kill -9 $(pgrep -f /opt/openvidu/openvidu-server) || true
|
||||
popd
|
||||
}
|
||||
|
||||
function stopMediaServer {
|
||||
# Remove Kurento Media Server
|
||||
declare -a arr=("kurento/kurento-media-server"
|
||||
"openvidu/mediasoup-controller:")
|
||||
for image in "${arr[@]}"; do
|
||||
docker ps -a | awk '{ print $1,$2 }' | grep "${image}" | awk '{ print $1 }' | xargs -I {} docker rm -f {} || true
|
||||
done
|
||||
docker ps -a
|
||||
}
|
||||
|
||||
# Environment variables
|
||||
if [[ -n ${1:-} ]]; then
|
||||
case "${1:-}" in
|
||||
--openvidu-server-unit-tests)
|
||||
OV_UNIT_TESTS=true
|
||||
;;
|
||||
--openvidu-server-integration-tests)
|
||||
OV_INTEGRATION_TESTS=true
|
||||
;;
|
||||
--openvidu-e2e-tests-kurento)
|
||||
OV_E2E_KURENTO=true
|
||||
;;
|
||||
--openvidu-e2e-tests-mediasoup)
|
||||
OV_E2E_MEDIASOUP=true
|
||||
;;
|
||||
--environment-launch-kurento)
|
||||
LAUNCH_OV_KURENTO=true
|
||||
;;
|
||||
--environment-launch-mediasoup)
|
||||
LAUNCH_OV_MEDIASOUP=true
|
||||
;;
|
||||
*)
|
||||
echo "Unrecognized method $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
else
|
||||
echo "Must provide a method to execute as first parameter when calling the script"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# openvidu-server unit tests
|
||||
# -------------
|
||||
if [[ "${OV_UNIT_TESTS}" == true ]]; then
|
||||
pushd openvidu-server
|
||||
mvn -B -Dtest=io.openvidu.server.test.unit.*Test test
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# openvidu-server integration tests
|
||||
# -------------
|
||||
if [[ "${OV_INTEGRATION_TESTS}" == true ]]; then
|
||||
pushd openvidu-server
|
||||
mvn -B -Dtest=io.openvidu.server.test.integration.*Test test
|
||||
popd
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# OpenVidu E2E Tests Kurento
|
||||
# -------------
|
||||
if [[ "${OV_E2E_KURENTO}" == true ]]; then
|
||||
openviduE2ETests "kurento"
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# OpenVidu E2E Tests mediasoup
|
||||
# -------------
|
||||
if [[ "${OV_E2E_MEDIASOUP}" == true ]]; then
|
||||
openviduE2ETests "mediasoup"
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Environment launch Kurento
|
||||
# -------------
|
||||
if [[ "${LAUNCH_OV_KURENTO}" == true ]]; then
|
||||
environmentLaunch "kurento"
|
||||
fi
|
||||
|
||||
# -------------
|
||||
# Environment launch mediasoup
|
||||
# -------------
|
||||
if [[ "${LAUNCH_OV_MEDIASOUP}" == true ]]; then
|
||||
environmentLaunch "mediasoup"
|
||||
fi
|
|
@ -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,10 @@
|
|||
{
|
||||
"singleQuote": true,
|
||||
"printWidth": 140,
|
||||
"trailingComma": "none",
|
||||
"semi": true,
|
||||
"bracketSpacing": true,
|
||||
"useTabs": false,
|
||||
"jsxSingleQuote": true,
|
||||
"tabWidth": 4
|
||||
}
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
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.
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
SEARCH_STRING_1="<K extends keyof SessionEventMap>(type: K,"
|
||||
REPLACE_STRING_1="<K extends keyof SessionEventMap>(type: K | string,"
|
||||
SEARCH_STRING_2='\[key: `signal:\${string}`\]: SignalEvent;'
|
||||
sed -i "s~${SEARCH_STRING_1}~${REPLACE_STRING_1}~g" ts4.4/lib/OpenVidu/Session.d.ts
|
||||
sed -i "/${SEARCH_STRING_2}/d" ts4.4/lib/OpenViduInternal/Events/EventMap/SessionEventMap.d.ts
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"include": ["../src"],
|
||||
"exclude": ["../config", "../docs", "../lib", "../node_modules", "../ts4.4"],
|
||||
"typedocOptions": {
|
||||
"name": "OpenVidu Browser",
|
||||
"entryPoints": ["../src/index.ts"],
|
||||
"out": "../docs",
|
||||
"theme": "default",
|
||||
"readme": "none",
|
||||
"includeVersion": true,
|
||||
"validation": {
|
||||
"notExported": true,
|
||||
"invalidLink": true
|
||||
},
|
||||
"excludeExternals": true,
|
||||
"excludePrivate": true,
|
||||
"excludeProtected": true,
|
||||
"excludeInternal": true
|
||||
}
|
||||
}
|
|
@ -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,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 --tsconfig ./config/tsconfig.json
|
||||
|
||||
# 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
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"author": "OpenVidu",
|
||||
"dependencies": {
|
||||
"freeice": "2.2.2",
|
||||
"hark": "1.2.3",
|
||||
"jsnlog": "2.30.0",
|
||||
"mime": "3.0.0",
|
||||
"platform": "1.3.6",
|
||||
"semver": "7.3.8",
|
||||
"uuid": "9.0.0",
|
||||
"wolfy87-eventemitter": "5.2.9",
|
||||
"events": "3.3.0",
|
||||
"inherits": "2.0.4"
|
||||
},
|
||||
"description": "OpenVidu Browser",
|
||||
"devDependencies": {
|
||||
"@types/node": "18.11.9",
|
||||
"@types/platform": "1.3.4",
|
||||
"browserify": "17.0.0",
|
||||
"grunt": "1.5.3",
|
||||
"grunt-cli": "1.4.3",
|
||||
"grunt-contrib-copy": "1.0.0",
|
||||
"grunt-contrib-sass": "2.0.0",
|
||||
"grunt-contrib-uglify": "5.2.2",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"grunt-postcss": "0.9.0",
|
||||
"grunt-string-replace": "1.3.3",
|
||||
"grunt-ts": "6.0.0-beta.22",
|
||||
"terser": "5.15.1",
|
||||
"tsify": "5.0.4",
|
||||
"tslint": "6.1.3",
|
||||
"typedoc": "0.23.21",
|
||||
"typescript": "4.9.3"
|
||||
},
|
||||
"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 && rm -rf ./ts4.4 && mkdir -p ./ts4.4/lib && cp -r ./lib ./ts4.4 && find ./ts4.4/lib -type f ! -iname '*.d.ts' -delete && ./config/replace_for_ts44.sh",
|
||||
"docs": "./generate-docs.sh"
|
||||
},
|
||||
"types": "lib/index.d.ts",
|
||||
"typesVersions": {
|
||||
"<4.4": {
|
||||
"*": [
|
||||
"ts4.4/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": "2.26.0"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { OpenVidu } from './OpenVidu/OpenVidu';
|
||||
import { JL } from 'jsnlog';
|
||||
|
||||
if (typeof globalThis !== 'undefined') {
|
||||
globalThis['OpenVidu'] = OpenVidu;
|
||||
}
|
||||
|
||||
// Disable jsnlog when library is loaded
|
||||
JL.setOptions({ enabled: false });
|
|
@ -0,0 +1,217 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link Session} and {@link 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 {@link Session.subscribe}
|
||||
* - `PUBLISHER`: SUBSCRIBER permissions + can publish their own Streams by calling {@link Session.publish}
|
||||
* - `MODERATOR`: SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection by call {@link Session.forceUnpublish} and {@link 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/#individual-recording-selection) <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" 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 {
|
||||
|
||||
if (!this.disposed) {
|
||||
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
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
logger.warn(`Connection ${this.connectionId} disposed when trying to send an ICE candidate. ICE candidate not sent`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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(): void {
|
||||
delete this.stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
dispose(): void {
|
||||
this.disposed = true;
|
||||
this.removeStream();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 '../OpenViduInternal/Events/Event';
|
||||
import { EventMap } from '../OpenViduInternal/Events/EventMap/EventMap';
|
||||
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
|
||||
|
||||
import EventEmitter = require('wolfy87-eventemitter');
|
||||
|
||||
/**
|
||||
* @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<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
|
||||
|
||||
/**
|
||||
* 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<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
abstract off<K extends keyof EventMap>(type: K, handler?: (event: EventMap[K]) => void): this;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
|
||||
const arrowHandler = (event) => {
|
||||
if (event) {
|
||||
logger.debug(message, event);
|
||||
} else {
|
||||
logger.debug(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.debug(message, event);
|
||||
} else {
|
||||
logger.debug(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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
offAux(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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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 {@link 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 finalParams;
|
||||
|
||||
const successExecMethod = (triggerEvent) => {
|
||||
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: finalParams };
|
||||
if (triggerEvent) {
|
||||
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'
|
||||
)
|
||||
]);
|
||||
}
|
||||
return resolve();
|
||||
};
|
||||
|
||||
if (this.type.startsWith('VB:')) {
|
||||
if (typeof params === 'string') {
|
||||
try {
|
||||
params = JSON.parse(params);
|
||||
} catch (error) {
|
||||
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Wrong params syntax: ' + error));
|
||||
}
|
||||
}
|
||||
|
||||
finalParams = params;
|
||||
|
||||
if (method === 'update') {
|
||||
if (!this.stream.virtualBackgroundSinkElements?.VB) {
|
||||
return reject(
|
||||
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
|
||||
);
|
||||
} else {
|
||||
this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
|
||||
.then(() => successExecMethod(false))
|
||||
.catch((error) => {
|
||||
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
|
||||
return reject(new OpenViduError(error.name, error.message));
|
||||
} else {
|
||||
return reject(
|
||||
new OpenViduError(
|
||||
OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
|
||||
'Error updating values on Virtual Background filter: ' + error
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return reject(
|
||||
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
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);
|
||||
return reject(errorMsg);
|
||||
}
|
||||
} else {
|
||||
stringParams = <string>params;
|
||||
}
|
||||
|
||||
finalParams = stringParams;
|
||||
|
||||
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) {
|
||||
return reject(
|
||||
new OpenViduError(
|
||||
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||
"You don't have permissions to execute a filter method"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
} else {
|
||||
return successExecMethod(true);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {@link 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) {
|
||||
return reject(
|
||||
new OpenViduError(
|
||||
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||
"You don't have permissions to add a filter event listener"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
} else {
|
||||
this.handlers.set(eventType, handler);
|
||||
logger.info(
|
||||
'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
|
||||
);
|
||||
return 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) {
|
||||
return reject(
|
||||
new OpenViduError(
|
||||
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
|
||||
"You don't have permissions to add a filter event listener"
|
||||
)
|
||||
);
|
||||
} else {
|
||||
return reject(error);
|
||||
}
|
||||
} else {
|
||||
this.handlers.delete(eventType);
|
||||
logger.info(
|
||||
'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
|
||||
);
|
||||
return resolve();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import Mime = require('mime/lite');
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
/**
|
||||
* Easy recording of {@link Stream} objects straightaway from the browser. Initialized with {@link OpenVidu.initLocalRecorder} method
|
||||
*/
|
||||
export class LocalRecorder {
|
||||
state: LocalRecorderState;
|
||||
|
||||
private connectionId: string;
|
||||
private mediaRecorder: MediaRecorder;
|
||||
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. {@link state} property must be `READY`. After method succeeds is set to `RECORDING`
|
||||
*
|
||||
* @param options The [MediaRecorder.options](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#parameters) to be used to record this Stream.
|
||||
* For example:
|
||||
*
|
||||
* ```javascript
|
||||
* var OV = new OpenVidu();
|
||||
* var publisher = await OV.initPublisherAsync();
|
||||
* var localRecorder = OV.initLocalRecorder(publisher.stream);
|
||||
* var options = {
|
||||
* mimeType: 'video/webm;codecs=vp8',
|
||||
* audioBitsPerSecond:128000,
|
||||
* videoBitsPerSecond:2500000
|
||||
* };
|
||||
* localRecorder.record(options);
|
||||
* ```
|
||||
*
|
||||
* If not specified, the default options preferred by the platform will be used.
|
||||
*
|
||||
* @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(options?: any): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
if (typeof options === 'string' || options instanceof String) {
|
||||
return reject(
|
||||
`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
|
||||
);
|
||||
}
|
||||
if (typeof MediaRecorder === 'undefined') {
|
||||
logger.error(
|
||||
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
|
||||
);
|
||||
throw Error(
|
||||
'MediaRecorder not supported on your device. 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 + "'");
|
||||
|
||||
if (!options) {
|
||||
options = { mimeType: 'video/webm' };
|
||||
} else if (!options.mimeType) {
|
||||
options.mimeType = 'video/webm';
|
||||
}
|
||||
|
||||
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
|
||||
this.mediaRecorder.start();
|
||||
} catch (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
this.mediaRecorder.ondataavailable = (e) => {
|
||||
if (e.data.size > 0) {
|
||||
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.state = LocalRecorderState.RECORDING;
|
||||
return resolve();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ends the recording of the Stream. {@link 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();
|
||||
return resolve();
|
||||
};
|
||||
this.mediaRecorder.stop();
|
||||
} catch (e) {
|
||||
return reject(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Pauses the recording of the Stream. {@link 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) {
|
||||
return 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;
|
||||
return resolve();
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resumes the recording of the Stream. {@link 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;
|
||||
return resolve();
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. {@link 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.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 {@link state} to `READY` so the recording can start again
|
||||
*/
|
||||
clean(): void {
|
||||
const f = () => {
|
||||
delete this.blob;
|
||||
this.chunks = [];
|
||||
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. {@link 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 = globalThis.URL.createObjectURL(<any>this.blob);
|
||||
a.href = url;
|
||||
a.download = this.id + '.' + Mime.getExtension(this.blob!.type);
|
||||
a.click();
|
||||
globalThis.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. {@link 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`. {@link 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) {
|
||||
return 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)
|
||||
return resolve(http.responseText);
|
||||
} else {
|
||||
return reject(http.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
http.send(this.blob);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. {@link 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) {
|
||||
return 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 + '.' + Mime.getExtension(this.blob!.type));
|
||||
|
||||
http.onreadystatechange = () => {
|
||||
if (http.readyState === 4) {
|
||||
if (http.status.toString().charAt(0) === '2') {
|
||||
// Success response from server (HTTP status standard: 2XX is success)
|
||||
return resolve(http.responseText);
|
||||
} else {
|
||||
return 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: this.mediaRecorder.mimeType });
|
||||
this.chunks = [];
|
||||
|
||||
this.videoPreviewSrc = globalThis.URL.createObjectURL(this.blob);
|
||||
|
||||
this.state = LocalRecorderState.FINISHED;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,866 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
|
||||
import { PublisherEventMap } from '../OpenViduInternal/Events/EventMap/PublisherEventMap';
|
||||
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
|
||||
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
|
||||
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';
|
||||
import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
|
||||
import { StreamEventReason } from '../OpenViduInternal/Events/Types/Types';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
let platform: PlatformUtils;
|
||||
|
||||
/**
|
||||
* Packs local media streams. Participants can publish it to a session. Initialized with {@link OpenVidu.initPublisher} method.
|
||||
*
|
||||
* See available event listeners at {@link PublisherEventMap}.
|
||||
*/
|
||||
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 {@link Publisher.subscribeToRemote} with value `true` or `false` (*false* by default)
|
||||
*/
|
||||
isSubscribedToRemote = false;
|
||||
|
||||
/**
|
||||
* The {@link 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 | undefined, 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: StreamEventReason) => {
|
||||
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 `enabled` value will have no effect
|
||||
*
|
||||
* #### Events dispatched
|
||||
*
|
||||
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
|
||||
*
|
||||
* The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
|
||||
* The {@link Publisher} object of the local participant will also dispatch the exact same event
|
||||
*
|
||||
* The {@link 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 {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
|
||||
*
|
||||
* See {@link StreamPropertyChangedEvent} to learn more.
|
||||
*
|
||||
* @param enabled `true` to publish the audio stream, `false` to unpublish it
|
||||
*/
|
||||
publishAudio(enabled: boolean): void {
|
||||
if (this.stream.audioActive !== enabled) {
|
||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
|
||||
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||
: this.stream.getMediaStream();
|
||||
affectedMediaStream.getAudioTracks().forEach((track) => {
|
||||
track.enabled = enabled;
|
||||
});
|
||||
if (!!this.session && !!this.stream.streamId) {
|
||||
this.session.openvidu.sendRequest(
|
||||
'streamPropertyChanged',
|
||||
{
|
||||
streamId: this.stream.streamId,
|
||||
property: 'audioActive',
|
||||
newValue: enabled,
|
||||
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', enabled, !enabled, 'publishAudio')
|
||||
]);
|
||||
this.emitEvent('streamPropertyChanged', [
|
||||
new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
|
||||
]);
|
||||
this.session.sendVideoData(this.stream.streamManager);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
this.stream.audioActive = enabled;
|
||||
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
|
||||
*
|
||||
* #### Events dispatched
|
||||
*
|
||||
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
|
||||
*
|
||||
* The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
|
||||
* The {@link Publisher} object of the local participant will also dispatch the exact same event
|
||||
*
|
||||
* The {@link 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 {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
|
||||
*
|
||||
* See {@link StreamPropertyChangedEvent} to learn more.
|
||||
*
|
||||
* @param enabled `true` to publish the video stream, `false` to unpublish it
|
||||
* @param resource
|
||||
*
|
||||
* If parameter **`enabled`** is `false`, this optional parameter is of type boolean. It can be set to `true` to forcibly free the hardware resource associated to the video track, or can be set to `false` to keep the access to the hardware resource.
|
||||
* Not freeing the resource makes the operation much more efficient, but depending on the platform two side-effects can be introduced: the video device may not be accessible by other applications and the access light of
|
||||
* webcams may remain on. This is platform-dependent: some browsers will not present the side-effects even when not freeing the resource.
|
||||
*
|
||||
* If parameter **`enabled`** is `true`, this optional parameter is of type [MediaStreamTrack](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack). It can be set to force the restoration of the video track with a custom track. This may be
|
||||
* useful if the Publisher was unpublished freeing the hardware resource, and openvidu-browser is not able to successfully re-create the video track as it was before unpublishing. In this way previous track settings will be ignored and this MediaStreamTrack
|
||||
* will be used instead.
|
||||
*/
|
||||
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (this.stream.videoActive !== enabled) {
|
||||
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
|
||||
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||
: this.stream.getMediaStream();
|
||||
let mustRestartMediaStream = false;
|
||||
affectedMediaStream.getVideoTracks().forEach((track) => {
|
||||
track.enabled = enabled;
|
||||
if (!enabled && resource === true) {
|
||||
track.stop();
|
||||
} else if (enabled && track.readyState === 'ended') {
|
||||
// Resource was freed
|
||||
mustRestartMediaStream = true;
|
||||
}
|
||||
});
|
||||
|
||||
// There is a Virtual Background filter applied that must be removed in case the hardware must be freed
|
||||
if (!enabled && resource === true && !!this.stream.filter && this.stream.filter.type.startsWith('VB:')) {
|
||||
this.stream.lastVBFilter = this.stream.filter; // Save the filter to be re-applied in case of unmute
|
||||
await this.stream.removeFilterAux(true);
|
||||
}
|
||||
|
||||
if (mustRestartMediaStream) {
|
||||
const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
|
||||
affectedMediaStream.removeTrack(oldVideoTrack);
|
||||
|
||||
const replaceVideoTrack = async (tr: MediaStreamTrack) => {
|
||||
affectedMediaStream.addTrack(tr);
|
||||
if (this.stream.isLocalStreamPublished) {
|
||||
await this.replaceTrackInRtcRtpSender(tr);
|
||||
}
|
||||
if (!!this.stream.lastVBFilter) {
|
||||
setTimeout(async () => {
|
||||
let options = this.stream.lastVBFilter!.options;
|
||||
const lastExecMethod = this.stream.lastVBFilter!.lastExecMethod;
|
||||
if (!!lastExecMethod && lastExecMethod.method === 'update') {
|
||||
options = Object.assign({}, options, lastExecMethod.params);
|
||||
}
|
||||
await this.stream.applyFilter(this.stream.lastVBFilter!.type, options);
|
||||
delete this.stream.lastVBFilter;
|
||||
}, 1);
|
||||
}
|
||||
};
|
||||
|
||||
if (!!resource && resource instanceof MediaStreamTrack) {
|
||||
await replaceVideoTrack(resource);
|
||||
} else {
|
||||
try {
|
||||
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: this.stream.lastVideoTrackConstraints
|
||||
});
|
||||
await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
|
||||
} catch (error) {
|
||||
return reject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!!this.session && !!this.stream.streamId) {
|
||||
this.session.openvidu.sendRequest(
|
||||
'streamPropertyChanged',
|
||||
{
|
||||
streamId: this.stream.streamId,
|
||||
property: 'videoActive',
|
||||
newValue: enabled,
|
||||
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',
|
||||
enabled,
|
||||
!enabled,
|
||||
'publishVideo'
|
||||
)
|
||||
]);
|
||||
this.emitEvent('streamPropertyChanged', [
|
||||
new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
|
||||
]);
|
||||
this.session.sendVideoData(this.stream.streamManager);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
this.stream.videoActive = enabled;
|
||||
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Call this method before {@link 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 {@link EventDispatcher.on}
|
||||
*/
|
||||
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
||||
super.on(<any>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 === 'accessAllowed') {
|
||||
if (this.accessAllowed) {
|
||||
this.emitEvent('accessAllowed', []);
|
||||
}
|
||||
}
|
||||
if (type === 'accessDenied') {
|
||||
if (this.accessDenied) {
|
||||
this.emitEvent('accessDenied', []);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link EventDispatcher.once}
|
||||
*/
|
||||
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
|
||||
super.once(<any>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 === 'accessAllowed') {
|
||||
if (this.accessAllowed) {
|
||||
this.emitEvent('accessAllowed', []);
|
||||
}
|
||||
}
|
||||
if (type === 'accessDenied') {
|
||||
if (this.accessDenied) {
|
||||
this.emitEvent('accessDenied', []);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link EventDispatcher.off}
|
||||
*/
|
||||
off<K extends keyof PublisherEventMap>(type: K, handler?: (event: PublisherEventMap[K]) => void): this {
|
||||
super.off(<any>type, handler);
|
||||
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 {@link 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> {
|
||||
return this.replaceTrackAux(track, true);
|
||||
}
|
||||
|
||||
/* Hidden methods */
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
initialize(): Promise<void> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let constraints: MediaStreamConstraints = {};
|
||||
let constraintsAux: MediaStreamConstraints = {};
|
||||
const timeForDialogEvent = 2000;
|
||||
let startTime;
|
||||
|
||||
const errorCallback = (openViduError: OpenViduError) => {
|
||||
this.accessDenied = true;
|
||||
this.accessAllowed = false;
|
||||
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
|
||||
return 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;
|
||||
}
|
||||
|
||||
// Set Content Hint on all MediaStreamTracks
|
||||
for (const track of mediaStream.getAudioTracks()) {
|
||||
if (!track.contentHint?.length) {
|
||||
// contentHint for audio: "", "speech", "speech-recognition", "music".
|
||||
// https://w3c.github.io/mst-content-hint/#audio-content-hints
|
||||
track.contentHint = '';
|
||||
logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
|
||||
}
|
||||
}
|
||||
for (const track of mediaStream.getVideoTracks()) {
|
||||
if (!track.contentHint?.length) {
|
||||
// contentHint for video: "", "motion", "detail", "text".
|
||||
// https://w3c.github.io/mst-content-hint/#video-content-hints
|
||||
switch (this.stream.typeOfVideo) {
|
||||
case TypeOfVideo.SCREEN:
|
||||
track.contentHint = 'detail';
|
||||
break;
|
||||
case TypeOfVideo.CUSTOM:
|
||||
logger.warn('CUSTOM type video track was provided without Content Hint!');
|
||||
track.contentHint = 'motion';
|
||||
break;
|
||||
case TypeOfVideo.CAMERA:
|
||||
case TypeOfVideo.IPCAM:
|
||||
default:
|
||||
track.contentHint = 'motion';
|
||||
break;
|
||||
}
|
||||
logger.info(`Video track Content Hint set: '${track.contentHint}'`);
|
||||
}
|
||||
}
|
||||
|
||||
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().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;
|
||||
const widthChanged = newWidth != null && newWidth !== this.stream.videoDimensions.width;
|
||||
const heightChanged = newHeight != null && newHeight !== this.stream.videoDimensions.height;
|
||||
if (this.stream.isLocalStreamPublished && (widthChanged || heightChanged)) {
|
||||
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', []);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
};
|
||||
|
||||
const getMediaSuccess = async (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);
|
||||
|
||||
try {
|
||||
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
||||
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 = async (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':
|
||||
try {
|
||||
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: constraints.video
|
||||
});
|
||||
mediaStream.getVideoTracks().forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
|
||||
errorMessage = error.toString();
|
||||
errorCallback(new OpenViduError(errorName, errorMessage));
|
||||
} catch (error) {
|
||||
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':
|
||||
try {
|
||||
const mediaStream = await navigator.mediaDevices.getUserMedia({
|
||||
audio: false,
|
||||
video: constraints.video
|
||||
});
|
||||
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 (error) {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
|
||||
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(), this.stream));
|
||||
} else {
|
||||
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);
|
||||
|
||||
try {
|
||||
if (this.stream.isSendScreen() && navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
|
||||
const mediaStream = await navigator.mediaDevices['getDisplayMedia']({ video: true });
|
||||
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
|
||||
await getMediaSuccess(mediaStream, definedAudioConstraint);
|
||||
} else {
|
||||
this.stream.lastVideoTrackConstraints = constraintsAux.video;
|
||||
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
|
||||
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
|
||||
await getMediaSuccess(mediaStream, definedAudioConstraint);
|
||||
}
|
||||
} catch (error) {
|
||||
await getMediaError(error);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
errorCallback(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
async replaceTrackAux(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void> {
|
||||
// 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
|
||||
this.replaceTrackInMediaStream(track, updateLastConstraints);
|
||||
return await this.replaceTrackInRtcRtpSender(track);
|
||||
} else {
|
||||
// Publisher not published. Simply replace the track on the local MediaStream
|
||||
return this.replaceTrackInMediaStream(track, updateLastConstraints);
|
||||
}
|
||||
} catch (error) {
|
||||
track.enabled = trackOriginalEnabledValue;
|
||||
throw 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(): 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);
|
||||
}
|
||||
|
||||
return 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.style.display = 'none';
|
||||
this.videoReference.muted = true;
|
||||
this.videoReference.autoplay = true;
|
||||
this.videoReference.controls = false;
|
||||
if (
|
||||
platform.isSafariBrowser() ||
|
||||
(platform.isIPhoneOrIPad() &&
|
||||
(platform.isChromeMobileBrowser() ||
|
||||
platform.isEdgeMobileBrowser() ||
|
||||
platform.isOperaMobileBrowser() ||
|
||||
platform.isFirefoxMobileBrowser()))
|
||||
) {
|
||||
this.videoReference.playsInline = true;
|
||||
}
|
||||
this.stream.setMediaStream(mediaStream);
|
||||
if (!!this.firstVideoElement) {
|
||||
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
|
||||
}
|
||||
this.videoReference.srcObject = this.stream.getMediaStream();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
|
||||
const mediaStream: MediaStream = this.stream.displayMyRemote()
|
||||
? this.stream.localMediaStreamWhenSubscribedToRemote!
|
||||
: this.stream.getMediaStream();
|
||||
let removedTrack: MediaStreamTrack;
|
||||
if (track.kind === 'video') {
|
||||
removedTrack = mediaStream.getVideoTracks()[0];
|
||||
if (updateLastConstraints) {
|
||||
this.stream.lastVideoTrackConstraints = track.getConstraints();
|
||||
}
|
||||
} else {
|
||||
removedTrack = mediaStream.getAudioTracks()[0];
|
||||
}
|
||||
removedTrack.enabled = false;
|
||||
removedTrack.stop();
|
||||
mediaStream.removeTrack(removedTrack);
|
||||
mediaStream.addTrack(track);
|
||||
const trackInfo = {
|
||||
oldLabel: removedTrack?.label || '',
|
||||
newLabel: track?.label || ''
|
||||
};
|
||||
if (track.kind === 'video' && updateLastConstraints) {
|
||||
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
|
||||
this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'videoTrack');
|
||||
if (this.stream.isLocalStreamPublished) {
|
||||
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
|
||||
}
|
||||
} else if (track.kind === 'audio' && updateLastConstraints) {
|
||||
this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'audioTrack');
|
||||
}
|
||||
if (track.kind === 'audio') {
|
||||
this.stream.disableHarkSpeakingEvent(false);
|
||||
this.stream.disableHarkStoppedSpeakingEvent(false);
|
||||
this.stream.disableHarkVolumeChangeEvent(false);
|
||||
this.stream.initHarkEvents();
|
||||
}
|
||||
}
|
||||
|
||||
/* 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', []);
|
||||
}
|
||||
}
|
||||
|
||||
private async replaceTrackInRtcRtpSender(track: MediaStreamTrack): Promise<void> {
|
||||
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) {
|
||||
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
|
||||
}
|
||||
} else if (track.kind === 'audio') {
|
||||
sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
|
||||
if (!sender) {
|
||||
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
|
||||
}
|
||||
} else {
|
||||
throw new Error('Unknown track kind ' + track.kind);
|
||||
}
|
||||
await (sender as RTCRtpSender).replaceTrack(track);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,621 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { StreamManagerEventMap } from '../OpenViduInternal/Events/EventMap/StreamManagerEventMap';
|
||||
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
|
||||
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
|
||||
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
|
||||
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;
|
||||
|
||||
/**
|
||||
* Interface in charge of displaying the media streams in the HTML DOM. This wraps any {@link Publisher} and {@link Subscriber} object.
|
||||
* You can insert as many video players fo the same Stream as you want by calling {@link StreamManager.addVideoElement} or
|
||||
* {@link 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
|
||||
*
|
||||
* See available event listeners at {@link StreamManagerEventMap}.
|
||||
*/
|
||||
export abstract 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 {@link Publisher}
|
||||
* - `true` for {@link Subscriber}
|
||||
*/
|
||||
remote: boolean;
|
||||
|
||||
/**
|
||||
* The DOM HTMLElement assigned as target element when creating the video for the Publisher/Subscriber. This property is only defined if:
|
||||
* - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
|
||||
* - {@link Subscriber} has been initialized by calling method {@link 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:
|
||||
* - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
|
||||
* - {@link Subscriber} has been initialized by calling method {@link 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.playsInline = true;
|
||||
}
|
||||
this.targetElement = targEl;
|
||||
this.element = targEl;
|
||||
}
|
||||
}
|
||||
|
||||
this.canPlayListener = () => {
|
||||
this.deactivateStreamPlayingEventExceptionTimeout();
|
||||
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* See {@link EventDispatcher.on}
|
||||
*/
|
||||
on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
||||
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') {
|
||||
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)]);
|
||||
}
|
||||
}
|
||||
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 {@link EventDispatcher.once}
|
||||
*/
|
||||
once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
|
||||
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') {
|
||||
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)]);
|
||||
}
|
||||
}
|
||||
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 {@link EventDispatcher.off}
|
||||
*/
|
||||
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
|
||||
super.offAux(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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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.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.isMirroredVideo(video)) {
|
||||
// Subscriber video associated to a previously mirrored video element
|
||||
this.removeMirrorVideo(video);
|
||||
} else if (!this.remote && !this.stream.displayMyRemote()) {
|
||||
// Publisher video
|
||||
video.muted = true;
|
||||
if (this.isMirroredVideo(video) && !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()) {
|
||||
// If the video is now set to mirror and is not screen share
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
abstract replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void;
|
||||
|
||||
/* 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: HTMLVideoElement): void {
|
||||
if (!platform.isIonicIos()) {
|
||||
video.style.transform = 'rotateY(180deg)';
|
||||
video.style.webkitTransform = 'rotateY(180deg)';
|
||||
}
|
||||
}
|
||||
|
||||
private removeMirrorVideo(video: HTMLVideoElement): void {
|
||||
video.style.transform = 'unset';
|
||||
video.style.webkitTransform = 'unset';
|
||||
}
|
||||
|
||||
private isMirroredVideo(video: HTMLVideoElement): boolean {
|
||||
return video.style.transform === 'rotateY(180deg)' || video.style.webkitTransform === 'rotateY(180deg)';
|
||||
}
|
||||
|
||||
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,101 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link Session.subscribe} method
|
||||
*
|
||||
* See available event listeners at {@link StreamManagerEventMap}.
|
||||
*/
|
||||
export class Subscriber extends StreamManager {
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
properties: SubscriberProperties;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(stream: Stream, targEl: string | HTMLElement | undefined, 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;
|
||||
}
|
||||
|
||||
/* Hidden methods */
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
|
||||
const mediaStream: MediaStream = this.stream.getMediaStream();
|
||||
let removedTrack: MediaStreamTrack;
|
||||
if (track.kind === 'video') {
|
||||
removedTrack = mediaStream.getVideoTracks()[0];
|
||||
if (updateLastConstraints) {
|
||||
this.stream.lastVideoTrackConstraints = track.getConstraints();
|
||||
}
|
||||
} else {
|
||||
removedTrack = mediaStream.getAudioTracks()[0];
|
||||
}
|
||||
mediaStream.removeTrack(removedTrack);
|
||||
removedTrack.stop();
|
||||
mediaStream.addTrack(track);
|
||||
}
|
||||
}
|
|
@ -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-2022 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,141 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link OpenViduError.name}
|
||||
*/
|
||||
export enum OpenViduErrorName {
|
||||
/**
|
||||
* Browser is not supported by OpenVidu.
|
||||
* Returned upon unsuccessful {@link 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 {@link OpenVidu.initPublisher} or {@link 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 {@link OpenVidu.initPublisher} or {@link 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 {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
|
||||
|
||||
/**
|
||||
* Browser does not support screen sharing.
|
||||
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
|
||||
|
||||
/**
|
||||
* Only for Chrome, there's no screen sharing extension installed
|
||||
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link 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 {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED',
|
||||
|
||||
/**
|
||||
* No video input device found with the provided deviceId (property {@link PublisherProperties.videoSource})
|
||||
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
INPUT_VIDEO_DEVICE_NOT_FOUND = 'INPUT_VIDEO_DEVICE_NOT_FOUND',
|
||||
|
||||
/**
|
||||
* No audio input device found with the provided deviceId (property {@link PublisherProperties.audioSource})
|
||||
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link 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 {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
INPUT_AUDIO_DEVICE_GENERIC_ERROR = 'INPUT_AUDIO_DEVICE_GENERIC_ERROR',
|
||||
|
||||
/**
|
||||
* Method {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia} has been called with properties `videoSource` and `audioSource` of
|
||||
* {@link PublisherProperties} parameter both set to *false* or *null*
|
||||
*/
|
||||
NO_INPUT_SOURCE_SET = 'NO_INPUT_SOURCE_SET',
|
||||
|
||||
/**
|
||||
* Some media property of {@link 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 {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
|
||||
*/
|
||||
PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR',
|
||||
|
||||
/**
|
||||
* The client tried to call a method without the required permissions. This can occur for methods {@link Session.publish},
|
||||
* {@link Session.forceUnpublish}, {@link Session.forceDisconnect}, {@link Stream.applyFilter}, {@link 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 {@link Session.connect}
|
||||
*/
|
||||
OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED',
|
||||
|
||||
/**
|
||||
* Error related to [Virtual Background](/en/stable/advanced-features/virtual-background/)
|
||||
*/
|
||||
VIRTUAL_BACKGROUND_ERROR = 'VIRTUAL_BACKGROUND_ERROR',
|
||||
|
||||
/**
|
||||
* 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,23 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 TypeOfVideo {
|
||||
CAMERA = 'CAMERA',
|
||||
SCREEN = 'SCREEN',
|
||||
CUSTOM = 'CUSTOM',
|
||||
IPCAM = 'IPCAM'
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link PublisherProperties.insertMode} and {@link 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,61 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { ConnectionEventReason } from './Types/Types';
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - {@link SessionEventMap.connectionCreated}
|
||||
* - {@link SessionEventMap.connectionDestroyed}
|
||||
*/
|
||||
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: ConnectionEventReason;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(cancelable: boolean, target: Session, type: string, connection: Connection, reason: ConnectionEventReason) {
|
||||
super(cancelable, target, type);
|
||||
this.connection = connection;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Triggered by {@link SessionEventMap.connectionPropertyChanged}
|
||||
*/
|
||||
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,83 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link Session.subscribe} or
|
||||
* by calling {@link Subscriber.createVideoElement}). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
|
||||
*
|
||||
* - `streamDestroyed`:
|
||||
* - If dispatched by a {@link 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 {@link OpenVidu.initPublisher} or by calling {@link Publisher.createVideoElement}). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event.
|
||||
* - If dispatched by {@link 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 {@link Session.subscribe} or
|
||||
* by calling {@link 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,21 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* All OpenVidu Browser events inherit from this interface
|
||||
*/
|
||||
export interface EventMap {}
|
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { StreamEvent } from '../StreamEvent';
|
||||
import { StreamManagerEventMap } from './StreamManagerEventMap';
|
||||
|
||||
/**
|
||||
* Events dispatched by {@link Publisher} object. Manage event listeners with
|
||||
* {@link Publisher.on}, {@link Publisher.once} and {@link Publisher.off} methods.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* publisher.on('accessDenied', () => {
|
||||
* console.error('Camera access has been denied!');
|
||||
* }
|
||||
*
|
||||
* publisher.off('accessDenied');
|
||||
* ```
|
||||
*/
|
||||
export interface PublisherEventMap extends StreamManagerEventMap {
|
||||
/**
|
||||
* Event dispatched when the {@link Publisher} has been published to the session (see {@link Session.publish}).
|
||||
*/
|
||||
streamCreated: StreamEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the {@link Publisher} has been unpublished from the session.
|
||||
*/
|
||||
streamDestroyed: StreamEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a Publisher tries to access some media input device and has the required permissions to do so.
|
||||
*
|
||||
* This happens when calling {@link OpenVidu.initPublisher} or {@link OpenVidu.initPublisherAsync} and the application
|
||||
* has permissions to use the devices. This usually means the user has accepted the permissions dialog that the
|
||||
* browser will show when trying to access the camera/microphone/screen.
|
||||
*/
|
||||
accessAllowed: never;
|
||||
|
||||
/**
|
||||
* Event dispatched when a Publisher tries to access some media input device and does NOT have the required permissions to do so.
|
||||
*
|
||||
* This happens when calling {@link OpenVidu.initPublisher} or {@link OpenVidu.initPublisherAsync} and the application
|
||||
* lacks the required permissions to use the devices. This usually means the user has NOT accepted the permissions dialog that the
|
||||
* browser will show when trying to access the camera/microphone/screen.
|
||||
*/
|
||||
accessDenied: never;
|
||||
|
||||
/**
|
||||
* Event dispatched when the pop-up shown by the browser to request permissions for the input media devices is opened.
|
||||
*
|
||||
* You can use this event to alert the user about granting permissions for your website. Note that this event is artificially
|
||||
* generated based only on time intervals when accessing media devices. A heavily overloaded client device that simply takes more
|
||||
* than usual to access the media device could produce a false trigger of this event.
|
||||
*/
|
||||
accessDialogOpened: never;
|
||||
|
||||
/**
|
||||
* Event dispatched after the user clicks on "Allow" or "Block" in the pop-up shown by the browser to request permissions
|
||||
* for the input media devices.
|
||||
*
|
||||
* This event can only be triggered after an {@link accessDialogOpened} event has been previously triggered.
|
||||
*/
|
||||
accessDialogClosed: never;
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { EventMap } from './EventMap';
|
||||
import { ConnectionEvent } from '../ConnectionEvent';
|
||||
import { ConnectionPropertyChangedEvent } from '../ConnectionPropertyChangedEvent';
|
||||
import { ExceptionEvent } from '../ExceptionEvent';
|
||||
import { NetworkQualityLevelChangedEvent } from '../NetworkQualityLevelChangedEvent';
|
||||
import { PublisherSpeakingEvent } from '../PublisherSpeakingEvent';
|
||||
import { RecordingEvent } from '../RecordingEvent';
|
||||
import { SessionDisconnectedEvent } from '../SessionDisconnectedEvent';
|
||||
import { SignalEvent } from '../SignalEvent';
|
||||
import { SpeechToTextEvent } from '../SpeechToTextEvent';
|
||||
import { StreamEvent } from '../StreamEvent';
|
||||
import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
|
||||
|
||||
/**
|
||||
* Events dispatched by {@link Session} object. Manage event listeners with
|
||||
* {@link Session.on}, {@link Session.once} and {@link Session.off} methods.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* session.on('connectionCreated', (event) => {
|
||||
* console.log('Connection ' + event.connection.connectionId + ' created');
|
||||
* }
|
||||
*
|
||||
* session.off('connectionDestroyed');
|
||||
* ```
|
||||
*/
|
||||
export interface SessionEventMap extends EventMap {
|
||||
/**
|
||||
* Event dispatched when a new user has connected to the session.
|
||||
*
|
||||
* It is fired for both the local user and remote users.
|
||||
*/
|
||||
connectionCreated: ConnectionEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a remote user has left the session.
|
||||
*
|
||||
* For the local user see {@link sessionDisconnected}.
|
||||
*/
|
||||
connectionDestroyed: ConnectionEvent;
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Event dispatched when a property of the local {@link Connection} object changes.
|
||||
*
|
||||
* It is fired only for the local user.
|
||||
*
|
||||
* The properties that may change are {@link Connection.role} and {@link Connection.record}.
|
||||
* The only way the Connection properties may change is by updating them through:
|
||||
*
|
||||
* - [API REST](/en/stable/reference-docs/REST-API/#patch-connection)
|
||||
* - [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>
|
||||
*/
|
||||
connectionPropertyChanged: ConnectionPropertyChangedEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the local user has left the session.
|
||||
*
|
||||
* For remote users see {@link connectionDestroyed}.
|
||||
*/
|
||||
sessionDisconnected: SessionDisconnectedEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a user has started publishing media to the session (see {@link Session.publish}).
|
||||
*
|
||||
* It is fired for both the local user and remote users.
|
||||
*/
|
||||
streamCreated: StreamEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a user stops publishing media to the session.
|
||||
*
|
||||
* It is fired for both the local user and remote users.
|
||||
*/
|
||||
streamDestroyed: StreamEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a Stream undergoes any change in any of its mutable properties
|
||||
* (see {@link StreamPropertyChangedEvent.changedProperty}).
|
||||
*
|
||||
* It is fired for both remote streams (owned by a {@link Subscriber}) or local streams (owned by a {@link Publisher}).
|
||||
*/
|
||||
streamPropertyChanged: StreamPropertyChangedEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a user has started speaking.
|
||||
*
|
||||
* It is fired for both the local user and remote users.
|
||||
*
|
||||
* Extra information:
|
||||
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
|
||||
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
|
||||
*/
|
||||
publisherStartSpeaking: PublisherSpeakingEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a user has stopped speaking.
|
||||
*
|
||||
* It is fired for both the local user and remote users.
|
||||
*
|
||||
* Extra information:
|
||||
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
|
||||
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
|
||||
*/
|
||||
publisherStopSpeaking: PublisherSpeakingEvent;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
[key: `signal:${string}`]: SignalEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a signal is received (see [Send text messages between users](/en/stable/cheatsheet/send-messages)).
|
||||
*
|
||||
* If the listener is added as **`signal:TYPE`**, only signals of type **`TYPE`** will trigger the event.
|
||||
*/
|
||||
signal: SignalEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the session has started being recorded.
|
||||
*
|
||||
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
|
||||
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
|
||||
*/
|
||||
recordingStarted: RecordingEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the session has stopped being recorded.
|
||||
*
|
||||
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
|
||||
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
|
||||
*/
|
||||
recordingStopped: RecordingEvent;
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Event dispatched when the session has started being broadcasted. See [Broadcast to YouTube/Twitch](/en/stable/advanced-features/broadcast/)
|
||||
*/
|
||||
broadcastStarted: never;
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Event dispatched when the session has stopped being broadcasted. See [Broadcast to YouTube/Twitch](/en/stable/advanced-features/broadcast/)
|
||||
*/
|
||||
broadcastStopped: never;
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Event dispatched when the network quality level of a {@link Connection} changes. See [network quality](/en/stable/advanced-features/network-quality/).
|
||||
*/
|
||||
networkQualityLevelChanged: NetworkQualityLevelChangedEvent;
|
||||
|
||||
/**
|
||||
* **This feature is part of OpenVidu
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
|
||||
* and
|
||||
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
|
||||
* editions**
|
||||
*
|
||||
* Event dispatched when a speech-to-text message has been received for certain Stream. See [Speech To Text](/en/stable/advanced-features/speech-to-text/).
|
||||
*/
|
||||
speechToTextMessage: SpeechToTextEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the local user has lost its connection to the session, and starts the automatic reconnection process.
|
||||
*
|
||||
* See [Reconnection events](/en/stable/advanced-features/automatic-reconnection/#reconnection-events).
|
||||
*/
|
||||
reconnecting: never;
|
||||
|
||||
/**
|
||||
* Event dispatched when the local user has successfully recovered its connection to the session after losing it.
|
||||
*
|
||||
* If the connection was recovered but OpenVidu Server already evicted the user due to timeout, then this event will
|
||||
* not be dispatched. A {@link sessionDisconnected} event with reason `networkDisconnect` will be triggered instead.
|
||||
*
|
||||
* See [Reconnection events](/en/stable/advanced-features/automatic-reconnection/#reconnection-events).
|
||||
*/
|
||||
reconnected: never;
|
||||
|
||||
/**
|
||||
* 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 {@link ExceptionEventName}.
|
||||
*/
|
||||
exception: ExceptionEvent;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { EventMap } from './EventMap';
|
||||
import { PublisherSpeakingEvent } from '../PublisherSpeakingEvent';
|
||||
import { StreamManagerEvent } from '../StreamManagerEvent';
|
||||
import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
|
||||
import { VideoElementEvent } from '../VideoElementEvent';
|
||||
|
||||
/**
|
||||
* Events dispatched by {@link StreamManager} object. Manage event listeners with
|
||||
* {@link StreamManager.on}, {@link StreamManager.once} and {@link StreamManager.off} methods.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```javascript
|
||||
* streamManager.on('videoElementCreated', (event) => {
|
||||
* console.log('New video element created:', event.element);
|
||||
* }
|
||||
*
|
||||
* streamManager.off('videoElementCreated');
|
||||
* ```
|
||||
*/
|
||||
export interface StreamManagerEventMap extends EventMap {
|
||||
/**
|
||||
* Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
|
||||
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
|
||||
*/
|
||||
videoElementCreated: VideoElementEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when an HTML video element has been removed from DOM by OpenVidu Browser library. See
|
||||
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
|
||||
*/
|
||||
videoElementDestroyed: VideoElementEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the 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).
|
||||
*/
|
||||
streamPlaying: StreamManagerEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the volume of the media stream's audio track changes. Only applies if {@link Stream.hasAudio} is `true`.
|
||||
* The frequency this event is fired with is defined by property `interval` of
|
||||
* {@link OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions} (default 100ms)
|
||||
*/
|
||||
streamAudioVolumeChange: StreamManagerEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when a Stream undergoes any change in any of its mutable properties
|
||||
* (see {@link StreamPropertyChangedEvent.changedProperty}).
|
||||
*/
|
||||
streamPropertyChanged: StreamPropertyChangedEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the user owning the stream has started speaking.
|
||||
*
|
||||
* Extra information:
|
||||
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
|
||||
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
|
||||
*/
|
||||
publisherStartSpeaking: PublisherSpeakingEvent;
|
||||
|
||||
/**
|
||||
* Event dispatched when the user owning the stream has stopped speaking.
|
||||
*
|
||||
* Extra information:
|
||||
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
|
||||
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
|
||||
*/
|
||||
publisherStopSpeaking: PublisherSpeakingEvent;
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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.
|
||||
*
|
||||
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link 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.
|
||||
*
|
||||
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link 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 {@link OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout},
|
||||
* which by default is 4000 milliseconds.
|
||||
*
|
||||
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Stream} object.
|
||||
*/
|
||||
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED',
|
||||
|
||||
/**
|
||||
* A {@link Subscriber} object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to {@link 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/stable/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/stable/cheatsheet/manage-videos/#you-take-care-of-the-video-players)).
|
||||
* Either way, whenever a {@link Subscriber} object is commanded to attach its {@link 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 {@link 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.
|
||||
*
|
||||
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Subscriber} object.
|
||||
*/
|
||||
NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT',
|
||||
|
||||
/**
|
||||
* There has been a server-side disconnection of the Speech To Text module. From the moment this exception is fired to the moment method
|
||||
* {@link Session.subscribeToSpeechToText} is called again, the transcription of the audio stream will not be available and no {@link SpeechToTextEvent}
|
||||
* will be fired.
|
||||
*
|
||||
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Session} object.
|
||||
*/
|
||||
SPEECH_TO_TEXT_DISCONNECTED = 'SPEECH_TO_TEXT_DISCONNECTED',
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered by {@link SessionEventMap.exception}
|
||||
*/
|
||||
export class ExceptionEvent extends Event {
|
||||
/**
|
||||
* Name of the exception
|
||||
*/
|
||||
name: ExceptionEventName;
|
||||
|
||||
/**
|
||||
* Object affected by the exception. Depending on the {@link ExceptionEvent.name} property:
|
||||
* - {@link Session}: `ICE_CANDIDATE_ERROR`
|
||||
* - {@link Stream}: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
|
||||
* - {@link 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,43 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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,56 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
|
||||
/**
|
||||
* Triggered by {@link SessionEventMap.networkQualityLevelChanged}
|
||||
*/
|
||||
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,54 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects)
|
||||
* - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects)
|
||||
*/
|
||||
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,71 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { RecordingEventReason } from './Types/Types';
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - {@link SessionEventMap.recordingStarted}
|
||||
* - {@link SessionEventMap.recordingStopped}
|
||||
*/
|
||||
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 {@link 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?: RecordingEventReason;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, type: string, id: string, name: string, reason?: RecordingEventReason) {
|
||||
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,80 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import { ConnectionEventReason } from './Types/Types';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* Triggered by {@link SessionEventMap.sessionDisconnected}
|
||||
*/
|
||||
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: ConnectionEventReason;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, reason: ConnectionEventReason) {
|
||||
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,64 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
|
||||
/**
|
||||
* Triggered by {@link SessionEventMap.signal}
|
||||
*/
|
||||
export class SignalEvent extends Event {
|
||||
/**
|
||||
* The type of signal. It is string `"signal"` for those signals sent with no {@link SignalOptions.type} property, and `"signal:type"` if was sent with a
|
||||
* valid {@link 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,72 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { SpeechToTextEventReason } from './Types/Types';
|
||||
|
||||
/**
|
||||
* Triggered by {@link SessionEventMap.speechToTextMessage}
|
||||
*/
|
||||
export class SpeechToTextEvent extends Event {
|
||||
|
||||
/**
|
||||
* The {@link Connection} owning the Stream that produced the speech-to-text event.
|
||||
* In other words, this is the participant that spoke and produced this transcription event.
|
||||
*/
|
||||
connection: Connection;
|
||||
|
||||
/**
|
||||
* The text of the event. This is the transcription for this specific piece of audio stream
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* All speech-to-text events are generated
|
||||
*/
|
||||
reason: SpeechToTextEventReason;
|
||||
|
||||
/**
|
||||
* The original event from the speech to text engine. This can vary depending on the engine
|
||||
*/
|
||||
raw: string;
|
||||
|
||||
/**
|
||||
* [BCP-47](https://tools.ietf.org/html/bcp47) language tag (like "en-US" or "es-ES") of the recognized text. This will be the same as the language provided
|
||||
* in method {@link Session.subscribeToSpeechToText} method
|
||||
*/
|
||||
lang: string;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(target: Session, connection: Connection, text: string, reason: SpeechToTextEventReason, raw: string, lang: string) {
|
||||
super(false, target, 'speechToTextMessage');
|
||||
this.connection = connection;
|
||||
this.text = text;
|
||||
this.reason = reason;
|
||||
this.raw = raw;
|
||||
this.lang = lang;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
// tslint:disable-next-line:no-empty
|
||||
callDefaultBehavior() { }
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import { StreamEventReason } from './Types/Types';
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
const logger: OpenViduLogger = OpenViduLogger.getInstance();
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - `streamCreated` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamCreated) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamCreated) objects)
|
||||
* - `streamDestroyed` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamDestroyed) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamDestroyed) objects)
|
||||
*/
|
||||
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: StreamEventReason;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
constructor(cancelable: boolean, target: Session | Publisher, type: string, stream: Stream, reason: StreamEventReason) {
|
||||
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,49 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - {@link StreamManagerEventMap.streamPlaying}
|
||||
* - {@link StreamManagerEventMap.streamAudioVolumeChange}
|
||||
*/
|
||||
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,83 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import { StreamPropertyChangedEventReason, ChangedPropertyType } from './Types/Types';
|
||||
|
||||
/**
|
||||
* Triggered by `streamPropertyChanged` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamPropertyChanged) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#streamPropertyChanged) objects)
|
||||
*/
|
||||
export class StreamPropertyChangedEvent extends Event {
|
||||
/**
|
||||
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property {@link Stream.connection}
|
||||
*/
|
||||
stream: Stream;
|
||||
|
||||
/**
|
||||
* The property of the stream that changed. This value is either `"videoActive"`, `"audioActive"`, `"videoTrack"`, `"audioTrack"`, `"videoDimensions"` or `"filter"`
|
||||
*/
|
||||
changedProperty: ChangedPropertyType;
|
||||
|
||||
/**
|
||||
* Cause of the change on the stream's property:
|
||||
* - For `videoActive`: `"publishVideo"`
|
||||
* - For `audioActive`: `"publishAudio"`
|
||||
* - For `videoTrack`: `"trackReplaced"`
|
||||
* - For `audioTrack`: `"trackReplaced"`
|
||||
* - For `videoDimensions`: `"deviceRotated"`, `"screenResized"` or `"trackReplaced"`
|
||||
* - For `filter`: `"applyFilter"`, `"execFilterMethod"` or `"removeFilter"`
|
||||
*/
|
||||
reason: StreamPropertyChangedEventReason;
|
||||
|
||||
/**
|
||||
* 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: ChangedPropertyType,
|
||||
newValue: Object,
|
||||
oldValue: Object,
|
||||
reason: StreamPropertyChangedEventReason
|
||||
) {
|
||||
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,42 @@
|
|||
export type ChangedPropertyType =
|
||||
'videoActive' |
|
||||
'audioActive' |
|
||||
'videoTrack' |
|
||||
'audioTrack' |
|
||||
'videoDimensions' |
|
||||
'filter';
|
||||
|
||||
export type StreamPropertyChangedEventReason =
|
||||
'publishVideo' |
|
||||
'publishAudio' |
|
||||
'trackReplaced' |
|
||||
'deviceRotated' |
|
||||
'screenResized' |
|
||||
'applyFilter' |
|
||||
'execFilterMethod' |
|
||||
'removeFilter';
|
||||
|
||||
export type ConnectionEventReason =
|
||||
'disconnect' |
|
||||
'forceDisconnectByUser' |
|
||||
'forceDisconnectByServer' |
|
||||
'sessionClosedByServer' |
|
||||
'networkDisconnect' |
|
||||
'nodeCrashed' |
|
||||
'';
|
||||
|
||||
export type StreamEventReason =
|
||||
ConnectionEventReason |
|
||||
'unpublish' |
|
||||
'forceUnpublishByUser' |
|
||||
'forceUnpublishByServer';
|
||||
|
||||
export type RecordingEventReason =
|
||||
'recordingStoppedByServer' |
|
||||
'sessionClosedByServer' |
|
||||
'automaticStop' |
|
||||
'nodeCrashed';
|
||||
|
||||
export type SpeechToTextEventReason =
|
||||
'recognizing' |
|
||||
'recognized';
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
|
||||
/**
|
||||
* Triggered by:
|
||||
* - {@link StreamManagerEventMap.videoElementCreated}
|
||||
* - {@link StreamManagerEventMap.videoElementDestroyed}
|
||||
*/
|
||||
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-2022 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,21 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 IceServerProperties {
|
||||
url: string;
|
||||
username?: string;
|
||||
credential?: string;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import { TypeOfVideo } from '../../Enums/TypeOfVideo';
|
||||
|
||||
export interface InboundStreamOptions {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
connection: Connection;
|
||||
hasAudio: boolean;
|
||||
hasVideo: boolean;
|
||||
audioActive: boolean;
|
||||
videoActive: boolean;
|
||||
typeOfVideo: TypeOfVideo;
|
||||
frameRate: number;
|
||||
videoDimensions: { width: number; height: number };
|
||||
filter?: Filter;
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
import { IceServerProperties } from './IceServerProperties';
|
||||
|
||||
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;
|
||||
coturnPort: number;
|
||||
turnUsername: string;
|
||||
turnCredential: string;
|
||||
version: string;
|
||||
mediaServer: string;
|
||||
videoSimulcast: boolean;
|
||||
life: number;
|
||||
customIceServers?: IceServerProperties[];
|
||||
recordingId?: string; // Defined if the session is being recorded and the client must be notified
|
||||
recordingName?: string; // Defined if the session is being recorded and the client must be notified
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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-2022 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-2022 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-2022 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,32 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { TypeOfVideo } from '../../Enums/TypeOfVideo';
|
||||
|
||||
export interface StreamOptionsServer {
|
||||
id: string;
|
||||
createdAt: number;
|
||||
hasAudio: boolean;
|
||||
hasVideo: boolean;
|
||||
audioActive: boolean;
|
||||
videoActive: boolean;
|
||||
typeOfVideo: TypeOfVideo;
|
||||
frameRate: number;
|
||||
videoDimensions: string;
|
||||
filter: Filter;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link Session.capabilities}
|
||||
*/
|
||||
export interface Capabilities {
|
||||
/**
|
||||
* `true` if the client can call {@link Session.forceDisconnect}, `false` if not
|
||||
*/
|
||||
forceDisconnect: boolean;
|
||||
|
||||
/**
|
||||
* `true` if the client can call {@link Session.forceUnpublish}, `false` if not
|
||||
*/
|
||||
forceUnpublish: boolean;
|
||||
|
||||
/**
|
||||
* `true` if the client can call {@link Session.publish}, `false` if not
|
||||
*/
|
||||
publish: boolean;
|
||||
|
||||
/**
|
||||
* `true` if the client can call {@link Session.subscribe}, `false` if not (true for every user for now)
|
||||
*/
|
||||
subscribe: boolean;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link OpenVidu.getDevices}
|
||||
*/
|
||||
export interface Device {
|
||||
/**
|
||||
* The kind of device
|
||||
*/
|
||||
kind: 'videoinput' | 'audioinput';
|
||||
|
||||
/**
|
||||
* Unique ID for the device. Use it on `audioSource` or `videoSource` properties of {@link 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,72 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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 {@link 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 {@link 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 {@link ExceptionEvent} of name [`ICE_CONNECTION_DISCONNECTED`](/en/stable/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 {@link ExceptionEvent} of name [`NO_STREAM_PLAYING_EVENT`](/en/stable/api/openvidu-browser/enums/ExceptionEventName.html#NO_STREAM_PLAYING_EVENT) to be fired.
|
||||
*
|
||||
* Default to `4000`.
|
||||
*/
|
||||
noStreamPlayingEventExceptionTimeout?: number;
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link OpenVidu.initPublisher}
|
||||
*/
|
||||
export interface PublisherProperties {
|
||||
/**
|
||||
* Which device should provide the audio source. Can be:
|
||||
* - Property `deviceId` of a {@link Device}
|
||||
* - A MediaStreamTrack obtained from a MediaStream object with {@link 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 {@link 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 {@link 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 {@link 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 {@link OpenVidu.getUserMedia}
|
||||
* - `false` or null to have an audio-only publisher
|
||||
* @default _Default camera_
|
||||
*/
|
||||
videoSource?: string | MediaStreamTrack | boolean;
|
||||
|
||||
/**
|
||||
* Use Simulcast video on WebRTC Publishers.
|
||||
* Senders will encode duplicate video streams with different qualities,
|
||||
* so the media server is able to select the most appropriate quality stream
|
||||
* for each Subscriber.
|
||||
* This setting is honored only if OpenVidu Server was configured to use the
|
||||
* mediasoup media server. Otherwise, Simulcast will be disabled.
|
||||
*/
|
||||
videoSimulcast?: 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,40 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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,57 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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:
|
||||
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing a valid `targetElement` parameter.
|
||||
* - {@link StreamManager.createVideoElement} has been called.
|
||||
*
|
||||
* This property is undefined when:
|
||||
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing *null* or *undefined* as `targetElement` parameter.
|
||||
* - {@link StreamManager.addVideoElement} has been called.
|
||||
*/
|
||||
targetElement?: HTMLElement;
|
||||
|
||||
/**
|
||||
* How the DOM video element should be inserted with respect to `targetElement`. This property is defined when:
|
||||
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing a valid `targetElement` parameter.
|
||||
* - {@link StreamManager.createVideoElement} has been called.
|
||||
*
|
||||
* This property is undefined when:
|
||||
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing *null* or *undefined* as `targetElement` parameter.
|
||||
* - {@link StreamManager.addVideoElement} has been called.
|
||||
*/
|
||||
insertMode?: VideoInsertMode;
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
||||
canplayListenerAdded: boolean;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 {@link 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 {@link 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 {@link Subscriber.subscribeToVideo}
|
||||
* @default true
|
||||
*/
|
||||
subscribeToVideo?: boolean;
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
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,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 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.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,161 @@
|
|||
/*
|
||||
* (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 addReconnectionQueryParamsIfMissing(uriString) {
|
||||
var searchParams = new URLSearchParams(new URL(uriString).search);
|
||||
if (!searchParams.has('reconnect')) {
|
||||
uriString = Array.from(searchParams).length > 0 ? uriString + '&reconnect=true' : uriString + '?reconnect=true';
|
||||
}
|
||||
return uriString;
|
||||
}
|
||||
|
||||
function reconnectAux(maxRetries, numRetries) {
|
||||
Logger.debug('Reconnection attempt #' + numRetries);
|
||||
ws.close(4104, 'Connection closed for reconnection');
|
||||
|
||||
wsUri = addReconnectionQueryParamsIfMissing(wsUri);
|
||||
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 = (code, reason) => {
|
||||
closing = true;
|
||||
ws.close(code, reason);
|
||||
};
|
||||
|
||||
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,687 @@
|
|||
/*
|
||||
* (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,85 @@
|
|||
/**
|
||||
* 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,5 @@
|
|||
var JsonRPC = require('./JsonRPC');
|
||||
var XmlRPC = require('./XmlRPC');
|
||||
|
||||
exports.JsonRPC = JsonRPC;
|
||||
exports.XmlRPC = XmlRPC;
|
|
@ -0,0 +1,41 @@
|
|||
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 = console.log),
|
||||
(this.info = console.info),
|
||||
(this.debug = console.debug),
|
||||
(this.warn = console.warn),
|
||||
(this.error = 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(globalThis.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 (
|
||||
globalThis['LOG_JSNLOG_RESULTS'] ||
|
||||
// If instance is created and it is OpenVidu Pro
|
||||
(this.instance &&
|
||||
openVidu.isAtLeastPro &&
|
||||
// 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) || (globalThis.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 (globalThis['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() {
|
||||
globalThis.console = this.defaultConsoleLogger.logger;
|
||||
globalThis.console.log = this.getConsoleWithJSNLog().log;
|
||||
globalThis.console.info = this.getConsoleWithJSNLog().info;
|
||||
globalThis.console.debug = this.getConsoleWithJSNLog().debug;
|
||||
globalThis.console.warn = this.getConsoleWithJSNLog().warn;
|
||||
globalThis.console.error = this.getConsoleWithJSNLog().error;
|
||||
}
|
||||
|
||||
private disableLogger() {
|
||||
JL.setOptions({ enabled: false });
|
||||
this.isJSNLogSetup = false;
|
||||
this.loggingSessionId = undefined;
|
||||
this.currentAppender = undefined;
|
||||
globalThis.console = this.defaultConsoleLogger.logger;
|
||||
globalThis.console.log = this.defaultConsoleLogger.log;
|
||||
globalThis.console.info = this.defaultConsoleLogger.info;
|
||||
globalThis.console.debug = this.defaultConsoleLogger.debug;
|
||||
globalThis.console.warn = this.defaultConsoleLogger.warn;
|
||||
globalThis.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[]) {
|
||||
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,233 @@
|
|||
// 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');
|
||||
*/
|
||||
|
||||
globalThis.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;
|
||||
}
|
||||
|
||||
globalThis.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
|
||||
globalThis.removeEventListener('message', onIFrameCallback);
|
||||
}
|
||||
|
||||
if (event.data.chromeExtensionStatus) {
|
||||
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
|
||||
|
||||
// this event listener is no more needed
|
||||
globalThis.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: globalThis.screen.width > 1920 ? globalThis.screen.width : 1920,
|
||||
maxHeight: globalThis.screen.height > 1080 ? globalThis.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
|
||||
globalThis.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);
|
||||
}
|
||||
|
||||
globalThis.getChromeExtensionStatus = function (callback) {
|
||||
// for Firefox:
|
||||
if (!!navigator.mozGetUserMedia) {
|
||||
callback('installed-enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
globalThis.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
|
||||
globalThis.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 = globalThis.getScreenId;
|
|
@ -0,0 +1,162 @@
|
|||
// 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,221 @@
|
|||
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 isNodeJs(): boolean {
|
||||
return platform.name === 'Node.js';
|
||||
}
|
||||
|
||||
/**
|
||||
* @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,592 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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 { TypeOfVideo } from '../Enums/TypeOfVideo';
|
||||
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;
|
||||
|
||||
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;
|
||||
typeOfVideo: TypeOfVideo | undefined;
|
||||
}
|
||||
|
||||
export class WebRtcPeer {
|
||||
pc: RTCPeerConnection;
|
||||
remoteCandidatesQueue: RTCIceCandidate[] = [];
|
||||
localCandidatesQueue: RTCIceCandidate[] = [];
|
||||
|
||||
// Same as WebRtcPeerConfiguration but without optional fields.
|
||||
protected configuration: Required<WebRtcPeerConfiguration>;
|
||||
|
||||
private iceCandidateList: RTCIceCandidate[] = [];
|
||||
|
||||
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()
|
||||
};
|
||||
// prettier-ignore
|
||||
logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`);
|
||||
|
||||
this.pc = new RTCPeerConnection({ iceServers: this.configuration.iceServers });
|
||||
|
||||
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
|
||||
if (event.candidate !== null) {
|
||||
// `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate:
|
||||
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate
|
||||
//
|
||||
// But in practice, it is actually an RTCIceCandidateInit that can be used to
|
||||
// obtain a proper candidate, using the RTCIceCandidate constructor:
|
||||
// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-constructor
|
||||
const candidateInit: RTCIceCandidateInit = event.candidate as RTCIceCandidateInit;
|
||||
const iceCandidate = new RTCIceCandidate(candidateInit);
|
||||
|
||||
this.configuration.onIceCandidate(iceCandidate);
|
||||
if (iceCandidate.candidate !== '') {
|
||||
this.localCandidatesQueue.push(iceCandidate);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pc.addEventListener('signalingstatechange', async () => {
|
||||
if (this.pc.signalingState === 'stable') {
|
||||
// SDP Offer/Answer finished. Add stored remote candidates.
|
||||
while (this.iceCandidateList.length > 0) {
|
||||
let candidate = this.iceCandidateList.shift();
|
||||
try {
|
||||
await this.pc.addIceCandidate(<RTCIceCandidate>candidate);
|
||||
} catch (error) {
|
||||
logger.error('Error when calling RTCPeerConnection#addIceCandidate for RTCPeerConnection ' + this.getId(), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED LEGACY METHOD: Old WebRTC versions don't implement
|
||||
// Transceivers, and instead depend on the deprecated
|
||||
// "offerToReceiveAudio" and "offerToReceiveVideo".
|
||||
private createOfferLegacy(): Promise<RTCSessionDescriptionInit> {
|
||||
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('[createOfferLegacy] RTCPeerConnection.createOffer() options:', JSON.stringify(options));
|
||||
|
||||
return this.pc.createOffer(options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an SDP offer from the local RTCPeerConnection to send to the other peer.
|
||||
* Only if the negotiation was initiated by this peer.
|
||||
*/
|
||||
async createOffer(): Promise<RTCSessionDescriptionInit> {
|
||||
// TODO: Delete this conditional when all supported browsers are
|
||||
// modern enough to implement the Transceiver methods.
|
||||
if (!('addTransceiver' in this.pc)) {
|
||||
logger.warn(
|
||||
'[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}'
|
||||
);
|
||||
return this.createOfferLegacy();
|
||||
} else {
|
||||
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) {
|
||||
throw new Error(
|
||||
`[WebRtcPeer.createOffer] Direction is '${this.configuration.mode}', but no stream was configured to be sent`
|
||||
);
|
||||
}
|
||||
|
||||
for (const track of this.configuration.mediaStream.getTracks()) {
|
||||
const tcInit: RTCRtpTransceiverInit = {
|
||||
direction: this.configuration.mode,
|
||||
streams: [this.configuration.mediaStream]
|
||||
};
|
||||
|
||||
if (track.kind === 'video' && this.configuration.simulcast) {
|
||||
// Check if the requested size is enough to ask for 3 layers.
|
||||
const trackSettings = track.getSettings();
|
||||
const trackConsts = track.getConstraints();
|
||||
|
||||
const trackWidth: number =
|
||||
trackSettings.width ?? (trackConsts.width as ConstrainULongRange).ideal ?? (trackConsts.width as number) ?? 0;
|
||||
const trackHeight: number =
|
||||
trackSettings.height ?? (trackConsts.height as ConstrainULongRange).ideal ?? (trackConsts.height as number) ?? 0;
|
||||
logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`);
|
||||
|
||||
const trackPixels = trackWidth * trackHeight;
|
||||
let maxLayers = 0;
|
||||
if (trackPixels >= 960 * 540) {
|
||||
maxLayers = 3;
|
||||
} else if (trackPixels >= 480 * 270) {
|
||||
maxLayers = 2;
|
||||
} else {
|
||||
maxLayers = 1;
|
||||
}
|
||||
|
||||
tcInit.sendEncodings = [];
|
||||
for (let l = 0; l < maxLayers; l++) {
|
||||
const layerDiv = 2 ** (maxLayers - l - 1);
|
||||
|
||||
const encoding: RTCRtpEncodingParameters = {
|
||||
rid: 'rdiv' + layerDiv.toString(),
|
||||
|
||||
// @ts-ignore -- Property missing from DOM types.
|
||||
scalabilityMode: 'L1T1'
|
||||
};
|
||||
|
||||
if (['detail', 'text'].includes(track.contentHint)) {
|
||||
// Prioritize best resolution, for maximum picture detail.
|
||||
encoding.scaleResolutionDownBy = 1.0;
|
||||
|
||||
// @ts-ignore -- Property missing from DOM types.
|
||||
encoding.maxFramerate = Math.floor(30 / layerDiv);
|
||||
} else {
|
||||
encoding.scaleResolutionDownBy = layerDiv;
|
||||
}
|
||||
|
||||
tcInit.sendEncodings.push(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
const tc = this.pc.addTransceiver(track, tcInit);
|
||||
|
||||
if (track.kind === 'video') {
|
||||
let sendParams = tc.sender.getParameters();
|
||||
let needSetParams = false;
|
||||
|
||||
if (!sendParams.degradationPreference?.length) {
|
||||
// degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution".
|
||||
// https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference
|
||||
if (['detail', 'text'].includes(track.contentHint)) {
|
||||
sendParams.degradationPreference = 'maintain-resolution';
|
||||
} else {
|
||||
sendParams.degradationPreference = 'balanced';
|
||||
}
|
||||
|
||||
logger.info(`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`);
|
||||
|
||||
// FIXME: Firefox implements degradationPreference on each individual encoding!
|
||||
// (set it on every element of the sendParams.encodings array)
|
||||
|
||||
needSetParams = true;
|
||||
}
|
||||
|
||||
// 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/v7.7.0/src/js/firefox/firefox_shim.js#L231-L255
|
||||
if (this.configuration.simulcast) {
|
||||
if (sendParams.encodings?.length !== tcInit.sendEncodings!.length) {
|
||||
sendParams.encodings = tcInit.sendEncodings!;
|
||||
|
||||
needSetParams = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needSetParams) {
|
||||
logger.debug(`[createOffer] Setting new RTCRtpSendParameters to video sender`);
|
||||
try {
|
||||
await tc.sender.setParameters(sendParams);
|
||||
} catch (error) {
|
||||
let message = `[WebRtcPeer.createOffer] Cannot set RTCRtpSendParameters to video sender`;
|
||||
if (error instanceof Error) {
|
||||
message += `: ${error.message}`;
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DEBUG: Uncomment for details.
|
||||
// if (track.kind === "video" && this.configuration.simulcast) {
|
||||
// // Print browser capabilities.
|
||||
// // prettier-ignore
|
||||
// logger.debug(`[createOffer] Transceiver send capabilities (static):\n${JSON.stringify(RTCRtpSender.getCapabilities?.("video"), null, 2)}`);
|
||||
// // prettier-ignore
|
||||
// logger.debug(`[createOffer] Transceiver recv capabilities (static):\n${JSON.stringify(RTCRtpReceiver.getCapabilities?.("video"), null, 2)}`);
|
||||
|
||||
// // Print requested Transceiver encodings and parameters.
|
||||
// // prettier-ignore
|
||||
// logger.debug(`[createOffer] Transceiver send encodings (requested):\n${JSON.stringify(tcInit.sendEncodings, null, 2)}`);
|
||||
// // prettier-ignore
|
||||
// logger.debug(`[createOffer] Transceiver send parameters (accepted):\n${JSON.stringify(tc.sender.getParameters(), null, 2)}`);
|
||||
// }
|
||||
}
|
||||
} 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]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let sdpOffer: RTCSessionDescriptionInit;
|
||||
try {
|
||||
sdpOffer = await this.pc.createOffer();
|
||||
} catch (error) {
|
||||
let message = `[WebRtcPeer.createOffer] Browser failed creating an SDP Offer`;
|
||||
if (error instanceof Error) {
|
||||
message += `: ${error.message}`;
|
||||
}
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
return sdpOffer;
|
||||
}
|
||||
|
||||
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 {
|
||||
return 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 as RTCPeerConnection).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);
|
||||
return resolve();
|
||||
} else {
|
||||
return 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') {
|
||||
return 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') {
|
||||
return 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') {
|
||||
return reject('RTCPeerConnection is closed when trying to set remote description');
|
||||
}
|
||||
this.setRemoteDescription(answer)
|
||||
.then(() => {
|
||||
// DEBUG: Uncomment for details.
|
||||
// {
|
||||
// const tc = this.pc.getTransceivers().find((tc) => tc.sender.track?.kind === "video");
|
||||
// // prettier-ignore
|
||||
// logger.debug(`[processRemoteAnswer] Transceiver send parameters (effective):\n${JSON.stringify(tc?.sender.getParameters(), null, 2)}`);
|
||||
// }
|
||||
|
||||
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,465 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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';
|
||||
}
|
||||
|
||||
return 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,51 @@
|
|||
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, OpenViduErrorName } from './OpenViduInternal/Enums/OpenViduError';
|
||||
export { TypeOfVideo } from './OpenViduInternal/Enums/TypeOfVideo';
|
||||
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 { SpeechToTextEvent } from './OpenViduInternal/Events/SpeechToTextEvent';
|
||||
export { ExceptionEvent, ExceptionEventName } 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';
|
||||
|
||||
export { EventMap } from './OpenViduInternal/Events/EventMap/EventMap';
|
||||
export { SessionEventMap } from './OpenViduInternal/Events/EventMap/SessionEventMap';
|
||||
export { StreamManagerEventMap } from './OpenViduInternal/Events/EventMap/StreamManagerEventMap';
|
||||
export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap';
|
||||
|
||||
export * from './OpenViduInternal/Events/Types/Types';
|
||||
|
||||
// Disable jsnlog when library is loaded
|
||||
JL.setOptions({ enabled: false });
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -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,14 @@
|
|||
[](http://www.apache.org/licenses/LICENSE-2.0)
|
||||
[](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml)
|
||||
[](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>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</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-2022 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,88 @@
|
|||
/*
|
||||
* (C) Copyright 2017-2022 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), WRONG_OPENVIDU_EDITION_ERROR_CODE(997),
|
||||
SERVICE_NOT_ENABLED_ERROR_CODE(996),
|
||||
|
||||
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_ERROR_CODE(407),
|
||||
PUBLISHER_ENDPOINT_NOT_FOUND_ERROR_CODE(408),
|
||||
|
||||
USER_METADATA_FORMAT_INVALID_ERROR_CODE(500),
|
||||
|
||||
SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601),
|
||||
|
||||
BROADCAST_CONCURRENT_ERROR_CODE(711), BROADCAST_START_ERROR_CODE(710), 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), MEDIA_NODE_CONNECTION_ERROR_CODE(902);
|
||||
|
||||
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-2022 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 + "]";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue