mirror of https://github.com/OpenVidu/openvidu.git
Compare commits
No commits in common. "master" and "v3.0.0" have entirely different histories.
|
@ -0,0 +1,113 @@
|
|||
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:7.0.1"
|
||||
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: ${{ 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: ${{ inputs.TEST_IMAGE || 'openvidu/openvidu-test-e2e:22.04' }}
|
||||
KURENTO_SNAPSHOTS_URL: ${{ secrets.KURENTO_SNAPSHOTS_URL }}
|
||||
KURENTO_MEDIA_SERVER_IMAGE: ${{ inputs.KURENTO_MEDIA_SERVER_IMAGE || 'kurento/kurento-media-server:7.0.1' }}
|
||||
KURENTO_JAVA_COMMIT: ${{ inputs.KURENTO_JAVA_COMMIT || 'default' }}
|
||||
DOCKER_RECORDING_VERSION: ${{ inputs.DOCKER_RECORDING_VERSION || 'default' }}
|
||||
CHROME_VERSION: ${{ inputs.CHROME_VERSION || 'latest' }}
|
||||
FIREFOX_VERSION: ${{ inputs.FIREFOX_VERSION || 'latest' }}
|
||||
EDGE_VERSION: ${{ inputs.EDGE_VERSION || 'latest' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup scripts
|
||||
run: |
|
||||
curl -sOJ --output-dir /opt https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/commons/build.sh
|
||||
curl -sOJ --output-dir /opt https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/commons/test-utils.sh
|
||||
cp ci-scripts/openvidu-e2e-tests.sh /opt/openvidu-e2e-tests.sh
|
||||
find /opt/*.sh -type f -print0 | xargs -0 chmod u+x
|
||||
- name: Clean environment
|
||||
run: /opt/build.sh --clean-environment
|
||||
- name: Prepare test environment
|
||||
run: /opt/test-utils.sh --prepare-test-environment "${TEST_IMAGE}"
|
||||
- name: Check and prepare kurento snapshots
|
||||
run: /opt/build.sh --check-and-prepare-kurento-snapshot
|
||||
- name: Use specific kurento-java commit
|
||||
if: ${{ env.KURENTO_JAVA_COMMIT != 'default'}}
|
||||
run: /opt/test-utils.sh --use-specific-kurento-java-commit
|
||||
- name: Build openvidu-browser
|
||||
run: /opt/build.sh --build-openvidu-browser
|
||||
- name: Build openvidu-node-client
|
||||
run: /opt/build.sh --build-openvidu-node-client
|
||||
- name: Build openvidu-java-client
|
||||
run: /opt/build.sh --build-openvidu-java-client
|
||||
- name: Build openvidu-parent
|
||||
run: /opt/build.sh --build-openvidu-parent
|
||||
- name: Build openvidu-testapp
|
||||
run: /opt/build.sh --build-openvidu-testapp
|
||||
- name: Build openvidu-server dashboard
|
||||
run: /opt/build.sh --build-openvidu-server-dashboard true
|
||||
- name: Build openvidu-server
|
||||
run: /opt/build.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/test-utils.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
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
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:
|
||||
nested_components_e2e_events:
|
||||
needs: test_setup
|
||||
name: Nested events
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -58,33 +58,74 @@ jobs:
|
|||
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
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:127.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
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- 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: Build openvidu-components-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-components-angular Testapp
|
||||
run: npm run build --prefix openvidu-components-angular
|
||||
- name: Serve openvidu-components-angular Testapp
|
||||
run: npm run start --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:4200; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- 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:
|
||||
unit_tests:
|
||||
name: Unit Tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: test_setup
|
||||
name: Nested Structural Directives
|
||||
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 Dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Run Unit Tests
|
||||
run: npm run lib:test --prefix openvidu-components-angular
|
||||
|
||||
|
||||
nested_components_e2e_directives:
|
||||
needs: test_setup
|
||||
name: Nested directives
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
|
@ -95,29 +136,53 @@ jobs:
|
|||
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
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:127.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
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-components-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-components-angular Testapp
|
||||
run: npm run build --prefix openvidu-components-angular
|
||||
- name: Serve openvidu-components-angular Testapp
|
||||
run: npm run start --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:4200; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run nested components E2E 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
|
||||
run: npm run e2e:nested-directives --prefix openvidu-components-angular
|
||||
|
||||
nested_attribute_directives:
|
||||
webcomponent_e2e_directives:
|
||||
needs: test_setup
|
||||
name: Nested Attribute Directives
|
||||
name: Webcomponent directives
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
|
@ -128,318 +193,439 @@ jobs:
|
|||
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
|
||||
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:127.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
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:lib-toolbar --prefix openvidu-components-angular
|
||||
- name: Cleanup
|
||||
if: always()
|
||||
uses: OpenVidu/actions/cleanup@main
|
||||
run: npm run e2e:webcomponent-directives --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_chat:
|
||||
needs: test_setup
|
||||
name: Webcomponent chat
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-chat --prefix openvidu-components-angular
|
||||
|
||||
|
||||
webcomponent_e2e_events:
|
||||
needs: test_setup
|
||||
name: Webcomponent 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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-events --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_media_devices:
|
||||
needs: test_setup
|
||||
name: Webcomponent media devices
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-media-devices --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_panels:
|
||||
needs: test_setup
|
||||
name: Webcomponent panels
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-panels --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_screen_sharing:
|
||||
needs: test_setup
|
||||
name: Webcomponent screen sharing
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-screensharing --prefix openvidu-components-angular
|
||||
|
||||
|
||||
webcomponent_e2e_stream:
|
||||
needs: test_setup
|
||||
name: Webcomponent stream
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-stream --prefix openvidu-components-angular
|
||||
|
||||
webcomponent_e2e_toolbar:
|
||||
needs: test_setup
|
||||
name: Webcomponent toolbar
|
||||
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: 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:127.0
|
||||
- name: Run openvidu-local-deployment
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
|
||||
cd openvidu-local-deployment/community
|
||||
./configure_lan_private_ip_linux.sh
|
||||
docker compose up -d
|
||||
- name: Run OpenVidu Call Backend
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/OpenVidu/openvidu-call
|
||||
cd openvidu-call/backend
|
||||
npm install
|
||||
npm run dev:start &
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd openvidu-components-angular
|
||||
npm install
|
||||
- name: Build openvidu-angular
|
||||
run: npm run lib:build --prefix openvidu-components-angular
|
||||
- name: Build openvidu-webcomponent
|
||||
run: npm run webcomponent:testing-build --prefix openvidu-components-angular
|
||||
- name: Serve Webcomponent Testapp
|
||||
run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
|
||||
- name: Wait for openvidu-local-deployment
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:7880; do
|
||||
echo "Waiting for openvidu-local-deployment to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Wait for openvidu-components-angular Testapp
|
||||
run: |
|
||||
until curl -s -f -o /dev/null http://localhost:8080; do
|
||||
echo "Waiting for openvidu-components-angular Testapp to be ready..."
|
||||
sleep 5
|
||||
done
|
||||
- name: Run Webcomponent E2E
|
||||
env:
|
||||
LAUNCH_MODE: CI
|
||||
run: npm run e2e:webcomponent-toolbar --prefix openvidu-components-angular
|
|
@ -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
|
||||
|
|
@ -39,7 +39,7 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
|
|||
|
||||
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">
|
||||
<img height="75px" src="https://openvidu.io/img/logos/support.jpg">
|
||||
|
||||
## Sponsors
|
||||
|
||||
|
|
|
@ -10,4 +10,9 @@
|
|||
node_modules
|
||||
dist/
|
||||
docs/
|
||||
openvidu-webcomponent/
|
||||
coverage/**
|
||||
e2e/webcomponent-app/openvidu-webcomponent-*.css
|
||||
e2e/webcomponent-app/openvidu-webcomponent-*.js
|
||||
|
||||
e2e/assets/*
|
|
@ -18,8 +18,7 @@
|
|||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": {
|
||||
"base": "dist/openvidu-components-testapp",
|
||||
"browser": ""
|
||||
"base": "dist/openvidu-components-testapp"
|
||||
},
|
||||
"index": "src/index.html",
|
||||
"polyfills": ["zone.js"],
|
||||
|
@ -149,6 +148,81 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"openvidu-webcomponent": {
|
||||
"projectType": "application",
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/openvidu-webcomponent-rc",
|
||||
"index": "src/index.html",
|
||||
"main": "src/app/openvidu-webcomponent/openvidu-webcomponent.main.ts",
|
||||
"polyfills": ["zone.js"],
|
||||
"tsConfig": "src/app/openvidu-webcomponent/tsconfig.openvidu-webcomponent.json",
|
||||
"aot": true,
|
||||
"assets": ["src/favicon.ico"],
|
||||
"styles": ["src/app/openvidu-webcomponent/openvidu-webcomponent.component.scss"],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "none",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1mb",
|
||||
"maximumError": "2mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
},
|
||||
"testing": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.testing.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "none",
|
||||
"sourceMap": false,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "1mb",
|
||||
"maximumError": "2mb"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "2kb",
|
||||
"maximumError": "4kb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
|
|
Binary file not shown.
|
@ -1,3 +1,4 @@
|
|||
export const LAUNCH_MODE = process.env.LAUNCH_MODE || 'DEV';
|
||||
export const OPENVIDU_CALL_SERVER = process.env.OPENVIDU_CALL_SERVER || 'http://localhost:6080';
|
||||
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
|
||||
let url = '';
|
||||
|
||||
describe('Testing Internal Directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
url = `${TestAppConfig.appUrl}&roomName=INTERNAL_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
} catch (error) {}
|
||||
await browser.sleep(500);
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should show/hide toolbar view recording button with toolbarViewRecordingsButton directive', async () => {
|
||||
await browser.get(`${url}&prejoin=false&toolbarViewRecordingsButton=true`);
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.toggleToolbarMoreOptions();
|
||||
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await browser.navigate().refresh();
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.toggleToolbarMoreOptions();
|
||||
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should show/hide participant name in prejoin with prejoinDisplayParticipantName directive', async () => {
|
||||
await browser.get(`${url}&prejoin=true`);
|
||||
await utils.checkPrejoinIsPresent();
|
||||
expect(await utils.isPresent('.participant-name-container')).toBeTrue();
|
||||
await browser.get(`${url}&prejoin=true&prejoinDisplayParticipantName=false`);
|
||||
await browser.navigate().refresh();
|
||||
await utils.checkPrejoinIsPresent();
|
||||
expect(await utils.isPresent('.participant-name-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should show/hide view recordings button with recordingActivityViewRecordingsButton directive', async () => {
|
||||
await browser.get(`${url}&prejoin=false&recordingActivityViewRecordingsButton=true`);
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.togglePanel('activities');
|
||||
await utils.clickOn('#recording-activity');
|
||||
expect(await utils.isPresent('#view-recordings-btn')).toBeTrue();
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await browser.navigate().refresh();
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.togglePanel('activities');
|
||||
await utils.clickOn('#recording-activity');
|
||||
expect(await utils.isPresent('#view-recordings-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should show/hide start/stop recording buttons with recordingActivityStartStopRecordingButton directive', async () => {
|
||||
await browser.get(`${url}&prejoin=false&recordingActivityStartStopRecordingButton=false`);
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.togglePanel('activities');
|
||||
await utils.clickOn('#recording-activity');
|
||||
expect(await utils.isPresent('#start-recording-btn')).toBeFalse();
|
||||
|
||||
await browser.sleep(3000);
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await browser.navigate().refresh();
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.togglePanel('activities');
|
||||
await utils.clickOn('#recording-activity');
|
||||
expect(await utils.isPresent('#start-recording-btn')).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -1,380 +0,0 @@
|
|||
import { Builder, By, WebDriver } from 'selenium-webdriver';
|
||||
|
||||
import { NestedConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = NestedConfig.appUrl;
|
||||
|
||||
describe('OpenVidu Components ATTRIBUTE toolbar directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(NestedConfig.browserName)
|
||||
.withCapabilities(NestedConfig.browserCapabilities)
|
||||
.setChromeOptions(NestedConfig.browserOptions)
|
||||
.usingServer(NestedConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should HIDE the CHAT PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#chatPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if chat button does not exist
|
||||
expect(await utils.isPresent('chat-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the PARTICIPANTS PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#participantsPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if participants button does not exist
|
||||
expect(await utils.isPresent('participants-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the ACTIVITIES PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#activitiesPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if participants button does not exist
|
||||
expect(await utils.isPresent('activities-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the DISPLAY LOGO', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#displayLogo-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('branding-logo')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the DISPLAY ROOM name', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#displayRoomName-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('session-name')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the FULLSCREEN button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#fullscreenButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.clickOn('#more-options-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#more-options-menu');
|
||||
|
||||
// Checking if fullscreen button is not present
|
||||
expect(await utils.isPresent('#fullscreen-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the STREAMING button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#broadcastingButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.clickOn('#more-options-btn');
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#more-options-menu');
|
||||
|
||||
// Checking if fullscreen button is not present
|
||||
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the LEAVE button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#leaveButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('leave-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the SCREENSHARE button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#screenshareButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('screenshare-btn')).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenVidu Components ATTRIBUTE stream directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(NestedConfig.browserName)
|
||||
.withCapabilities(NestedConfig.browserCapabilities)
|
||||
.setChromeOptions(NestedConfig.browserOptions)
|
||||
.usingServer(NestedConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should HIDE the AUDIO detector', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#displayAudioDetection-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#session-container');
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('audio-wave-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the PARTICIPANT NAME', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#displayParticipantName-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#session-container');
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('participant-name-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the SETTINGS button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#settingsButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('settings-container')).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenVidu Components ATTRIBUTE participant panels directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(NestedConfig.browserName)
|
||||
.withCapabilities(NestedConfig.browserCapabilities)
|
||||
.setChromeOptions(NestedConfig.browserOptions)
|
||||
.usingServer(NestedConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should HIDE the participant MUTE button', async () => {
|
||||
const fixedSession = `${url}?sessionId=fixedNameTesting`;
|
||||
await browser.get(`${fixedSession}`);
|
||||
|
||||
await utils.clickOn('#ovParticipantPanelItem-checkbox');
|
||||
|
||||
await utils.clickOn('#muteButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#participants-panel-btn');
|
||||
|
||||
await utils.waitForElement('#participants-container');
|
||||
|
||||
// Starting new browser for adding a new participant
|
||||
const newTabScript = `window.open("${fixedSession}")`;
|
||||
await browser.executeScript(newTabScript);
|
||||
|
||||
// Get tabs opened
|
||||
const tabs = await browser.getAllWindowHandles();
|
||||
// Focus on the last tab
|
||||
browser.switchTo().window(tabs[1]);
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
// Switch to first tab
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
|
||||
await utils.waitForElement('#remote-participant-item');
|
||||
|
||||
expect(await utils.isPresent('mute-btn')).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('OpenVidu Components ATTRIBUTE activity panel directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(NestedConfig.browserName)
|
||||
.withCapabilities(NestedConfig.browserCapabilities)
|
||||
.setChromeOptions(NestedConfig.browserOptions)
|
||||
.usingServer(NestedConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should HIDE the RECORDING activity', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovActivitiesPanel-checkbox');
|
||||
|
||||
await utils.clickOn('#recordingActivity-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#activities-panel-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#custom-activities-panel');
|
||||
|
||||
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the STREAMING activity', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovActivitiesPanel-checkbox');
|
||||
|
||||
await utils.clickOn('#broadcastingActivity-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#activities-panel-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#custom-activities-panel');
|
||||
|
||||
await utils.waitForElement('ov-recording-activity');
|
||||
|
||||
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@ import { OpenViduComponentsPO } from '../utils.po.test';
|
|||
|
||||
const url = NestedConfig.appUrl;
|
||||
|
||||
describe('E2E: Toolbar structural directive scenarios', () => {
|
||||
describe('Testing TOOLBAR STRUCTURAL DIRECTIVES', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
|
@ -24,13 +24,10 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should render only the custom toolbar (no additional buttons, no default toolbar)', async () => {
|
||||
it('should inject the custom TOOLBAR without additional buttons', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
@ -48,7 +45,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom toolbar with additional custom buttons and hide the default toolbar', async () => {
|
||||
it('should inject the custom TOOLBAR with additional buttons', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
@ -72,7 +69,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom toolbar with additional custom panel buttons and hide the default toolbar', async () => {
|
||||
it('should inject the custom TOOLBAR with additional PANEL buttons', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
@ -96,7 +93,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#default-toolbar')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only additional toolbar buttons (default toolbar visible, no custom toolbar)', async () => {
|
||||
it('should inject the TOOLBAR ADDITIONAL BUTTONS only', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
|
@ -119,7 +116,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only additional toolbar panel buttons (default toolbar visible, no custom toolbar)', async () => {
|
||||
it('should inject the TOOLBAR ADDITIONAL PANEL BUTTONS only', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
|
@ -136,14 +133,14 @@ describe('E2E: Toolbar structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
|
||||
|
||||
element = await browser.findElements(By.id('toolbar-additional-panel-btn'));
|
||||
expect(element.length).toEqual(1);
|
||||
expect(element.length).toEqual(2);
|
||||
|
||||
// Check if custom toolbar not is present
|
||||
expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe('E2E: Panel structural directive scenarios', () => {
|
||||
describe('Testing PANEL STRUCTURAL DIRECTIVES', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
|
@ -162,49 +159,10 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should render an additional custom panel with default panels', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovAdditionalPanels-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
// Check if toolbar panel buttons are present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open additional panel
|
||||
await utils.clickOn('#toolbar-additional-panel-btn');
|
||||
await browser.sleep(500);
|
||||
|
||||
// Check if custom panel is present
|
||||
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
|
||||
element = await utils.waitForElement('#additional-panel-title');
|
||||
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
|
||||
|
||||
// Open the participants panel
|
||||
await utils.clickOn('#participants-panel-btn');
|
||||
await browser.sleep(500);
|
||||
|
||||
// Check if default panel is present
|
||||
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
|
||||
|
||||
// Open additional panel again
|
||||
await utils.clickOn('#toolbar-additional-panel-btn');
|
||||
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
|
||||
|
||||
// Close the additional panel
|
||||
await utils.clickOn('#toolbar-additional-panel-btn');
|
||||
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only the custom panel container (no children, no default panels)', async () => {
|
||||
it('should inject the CUSTOM PANEL without children', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -240,7 +198,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with an additional panel only', async () => {
|
||||
it('should inject the CUSTOM PANEL with ADDITIONAL PANEL only', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
|
@ -269,7 +227,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with a custom chat panel only', async () => {
|
||||
it('should inject the CUSTOM PANEL with CHAT PANEL only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -308,7 +266,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with a custom activities panel only', async () => {
|
||||
it('should inject the CUSTOM PANEL with ACTIVITIES PANEL only', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
|
@ -335,7 +293,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await element.getAttribute('innerText')).toEqual('CUSTOM ACTIVITIES PANEL');
|
||||
});
|
||||
|
||||
it('should render the custom panel container with a custom participants panel only (no children)', async () => {
|
||||
it('should inject the CUSTOM PANEL with PARTICIPANTS PANEL only and without children', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -373,7 +331,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with a custom participants panel and a custom participant item', async () => {
|
||||
it('should inject the CUSTOM PANEL with PARTICIPANTS PANEL and P ITEM only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -412,7 +370,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with a custom participants panel, custom participant item, and custom item element', async () => {
|
||||
it('should inject the CUSTOM PANEL with PARTICIPANTS PANEL and P ITEM and P ITEM ELEMENT', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -457,7 +415,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only a custom activities panel (no default panel)', async () => {
|
||||
it('should inject an ACTIVITIES PANEL only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovActivitiesPanel-checkbox');
|
||||
|
@ -482,7 +440,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#activities-container')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should render only a custom additional panel (no default panel)', async () => {
|
||||
it('should inject an ADDITIONAL PANEL only', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}`);
|
||||
|
||||
|
@ -508,7 +466,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only a custom chat panel (no custom panel container)', async () => {
|
||||
it('should inject the CHAT PANEL only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovChatPanel-checkbox');
|
||||
|
@ -546,7 +504,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should render only a custom participants panel (no custom panel container)', async () => {
|
||||
it('should inject the PARTICIPANTS PANEL only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovParticipantsPanel-checkbox');
|
||||
|
@ -584,7 +542,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only a custom participant panel item (no custom panel container)', async () => {
|
||||
it('should inject the PARTICIPANTS PANEL ITEM only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovParticipantPanelItem-checkbox');
|
||||
|
@ -625,7 +583,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render only a custom participant panel item element (no custom panel container)', async () => {
|
||||
it('should inject the PARTICIPANTS PANEL ITEM ELEMENT only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
|
||||
|
@ -663,7 +621,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom panel container with both custom chat and participants panels', async () => {
|
||||
it('should inject the CUSTOM PANEL with CHAT and PARTICIPANTS PANELS', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovPanel-checkbox');
|
||||
|
@ -704,7 +662,7 @@ describe('E2E: Panel structural directive scenarios', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('E2E: Layout and stream structural directive scenarios', () => {
|
||||
describe('Testing LAYOUT STRUCTURAL DIRECTIVES', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
|
@ -723,13 +681,10 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should render only the custom layout (no stream, no default layout)', async () => {
|
||||
it('should inject the custom LAYOUT WITHOUT STREAM', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovLayout-checkbox');
|
||||
|
@ -750,7 +705,7 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('video')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should render the custom layout with a custom stream (no default layout/stream)', async () => {
|
||||
it('should inject the custom LAYOUT WITH STREAM', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovLayout-checkbox');
|
||||
|
@ -778,7 +733,7 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('video')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should render only a custom stream (no custom layout, no default stream)', async () => {
|
||||
it('should inject the CUSTOM STREAM only', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
@ -804,3 +759,293 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
|
|||
expect(await utils.isPresent('video')).toBeTrue();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Testing ATTRIBUTE DIRECTIVES', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(NestedConfig.browserName)
|
||||
.withCapabilities(NestedConfig.browserCapabilities)
|
||||
.setChromeOptions(NestedConfig.browserOptions)
|
||||
.usingServer(NestedConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should HIDE the CHAT PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#chatPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if chat button does not exist
|
||||
expect(await utils.isPresent('chat-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the PARTICIPANTS PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#participantsPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if participants button does not exist
|
||||
expect(await utils.isPresent('participants-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the ACTIVITIES PANEL BUTTON', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#activitiesPanelButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Check if participants button does not exist
|
||||
expect(await utils.isPresent('activities-panel-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the DISPLAY LOGO', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#displayLogo-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('branding-logo')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the DISPLAY ROOM name', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#displayRoomName-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('session-name')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the FULLSCREEN button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#fullscreenButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.clickOn('#more-options-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#more-options-menu');
|
||||
|
||||
// Checking if fullscreen button is not present
|
||||
expect(await utils.isPresent('#fullscreen-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the STREAMING button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#broadcastingButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.clickOn('#more-options-btn');
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#more-options-menu');
|
||||
|
||||
// Checking if fullscreen button is not present
|
||||
expect(await utils.isPresent('#broadcasting-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the LEAVE button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#leaveButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('leave-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the SCREENSHARE button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
||||
await utils.clickOn('#screenshareButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
expect(await utils.isPresent('screenshare-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the AUDIO detector', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#displayAudioDetection-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#session-container');
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('audio-wave-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the PARTICIPANT NAME', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#displayParticipantName-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#session-container');
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('participant-name-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the SETTINGS button', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovStream-checkbox');
|
||||
|
||||
await utils.clickOn('#settingsButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.waitForElement('#custom-stream');
|
||||
|
||||
expect(await utils.isPresent('settings-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the participant MUTE button', async () => {
|
||||
const fixedSession = `${url}?sessionId=fixedNameTesting`;
|
||||
await browser.get(`${fixedSession}`);
|
||||
|
||||
await utils.clickOn('#ovParticipantPanelItem-checkbox');
|
||||
|
||||
await utils.clickOn('#muteButton-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#participants-panel-btn');
|
||||
|
||||
await utils.waitForElement('#participants-container');
|
||||
|
||||
// Starting new browser for adding a new participant
|
||||
const newTabScript = `window.open("${fixedSession}")`;
|
||||
await browser.executeScript(newTabScript);
|
||||
|
||||
// Get tabs opened
|
||||
const tabs = await browser.getAllWindowHandles();
|
||||
// Focus on the last tab
|
||||
browser.switchTo().window(tabs[1]);
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
// Switch to first tab
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
|
||||
await utils.waitForElement('#remote-participant-item');
|
||||
|
||||
expect(await utils.isPresent('mute-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the RECORDING activity', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovActivitiesPanel-checkbox');
|
||||
|
||||
await utils.clickOn('#recordingActivity-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#activities-panel-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#custom-activities-panel');
|
||||
|
||||
expect(await utils.isPresent('ov-recording-activity')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the STREAMING activity', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovActivitiesPanel-checkbox');
|
||||
|
||||
await utils.clickOn('#broadcastingActivity-checkbox');
|
||||
|
||||
await utils.clickOn('#apply-btn');
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#activities-panel-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#custom-activities-panel');
|
||||
|
||||
await utils.waitForElement('ov-recording-activity');
|
||||
|
||||
expect(await utils.isPresent('ov-broadcasting-activity')).toBeFalse();
|
||||
});
|
||||
});
|
|
@ -5,7 +5,7 @@ import { OpenViduComponentsPO } from '../utils.po.test';
|
|||
|
||||
const url = NestedConfig.appUrl;
|
||||
|
||||
describe('OpenVidu Components EVENTS', () => {
|
||||
describe('Testing EVENTS', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
|
@ -24,13 +24,10 @@ describe('OpenVidu Components EVENTS', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should receive the onParticipantLeft event', async () => {
|
||||
it('should receive the onRoomDisconnected event', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.clickOn('#ovToolbar-checkbox');
|
||||
|
@ -43,8 +40,8 @@ describe('OpenVidu Components EVENTS', () => {
|
|||
await utils.clickOn('#leave-btn');
|
||||
|
||||
// Checking if onLeaveButtonClicked has been received
|
||||
await utils.waitForElement('#onParticipantLeft');
|
||||
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
|
||||
await utils.waitForElement('#onRoomDisconnected');
|
||||
expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should receive the onVideoEnabledChanged event', async () => {
|
||||
|
|
|
@ -10,14 +10,12 @@ interface BrowserConfig {
|
|||
browserName: string;
|
||||
}
|
||||
|
||||
const audioPath = LAUNCH_MODE === 'CI' ? `e2e-assets/audio_test.wav` : 'e2e/assets/audio_test.wav';
|
||||
|
||||
const chromeArguments = [
|
||||
'--window-size=1300,1000',
|
||||
// '--headless',
|
||||
'--headless',
|
||||
'--use-fake-ui-for-media-stream',
|
||||
'--use-fake-device-for-media-stream',
|
||||
`--use-file-for-fake-audio-capture=${audioPath}`
|
||||
'--use-file-for-fake-audio-capture=e2e/assets/audio.wav'
|
||||
];
|
||||
const chromeArgumentsCI = [
|
||||
'--window-size=1300,1000',
|
||||
|
@ -31,10 +29,7 @@ const chromeArgumentsCI = [
|
|||
'--disable-background-networking',
|
||||
'--disable-default-apps',
|
||||
'--use-fake-ui-for-media-stream',
|
||||
'--use-fake-device-for-media-stream',
|
||||
`--use-file-for-fake-audio-capture=${audioPath}`,
|
||||
'--autoplay-policy=no-user-gesture-required',
|
||||
'--allow-file-access-from-files'
|
||||
'--use-fake-device-for-media-stream'
|
||||
];
|
||||
const chromeArgumentsWithoutMediaDevices = ['--headless', '--window-size=1300,900', '--deny-permission-prompts'];
|
||||
const chromeArgumentsWithoutMediaDevicesCI = [
|
||||
|
@ -51,8 +46,8 @@ const chromeArgumentsWithoutMediaDevicesCI = [
|
|||
'--deny-permission-prompts'
|
||||
];
|
||||
|
||||
export const TestAppConfig: BrowserConfig = {
|
||||
appUrl: 'http://localhost:4200/#/call?staticVideos=false',
|
||||
export const WebComponentConfig: BrowserConfig = {
|
||||
appUrl: 'http://localhost:8080/',
|
||||
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
|
||||
browserName: 'chrome',
|
||||
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),
|
||||
|
|
|
@ -136,42 +136,6 @@ export class OpenViduComponentsPO {
|
|||
await this.clickOn('#fullscreen-btn');
|
||||
}
|
||||
|
||||
async leaveRoom() {
|
||||
try {
|
||||
// Close any open panels or menus clicking on the body
|
||||
await this.clickOn('body');
|
||||
await this.browser.sleep(300);
|
||||
|
||||
// Verify that the leave button is present
|
||||
await this.waitForElement('#leave-btn');
|
||||
|
||||
// Click on the leave button
|
||||
await this.clickOn('#leave-btn');
|
||||
|
||||
// Verify that the session container is no longer present
|
||||
await this.browser.wait(
|
||||
async () => {
|
||||
return !(await this.isPresent('#session-container'));
|
||||
},
|
||||
this.TIMEOUT,
|
||||
'Session container should disappear after leaving room'
|
||||
);
|
||||
|
||||
// Wait for the prejoin container to be present again
|
||||
await this.browser.sleep(500);
|
||||
|
||||
// Verify that there are no video elements left in the DOM
|
||||
const videoCount = await this.getNumberOfElements('video');
|
||||
if (videoCount > 0) {
|
||||
console.warn(`Warning: ${videoCount} video elements still present after leaving room`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during leaveRoom:', error);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async togglePanel(panelName: string) {
|
||||
switch (panelName) {
|
||||
case 'activities':
|
||||
|
@ -195,7 +159,5 @@ export class OpenViduComponentsPO {
|
|||
await this.clickOn('#toolbar-settings-btn');
|
||||
break;
|
||||
}
|
||||
|
||||
await this.browser.sleep(500);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,277 @@
|
|||
import monkeyPatchMediaDevices from './utils/media-devices.js';
|
||||
|
||||
var MINIMAL;
|
||||
var LANG;
|
||||
var CAPTIONS_LANG;
|
||||
var CUSTOM_LANG_OPTIONS;
|
||||
var CUSTOM_CAPTIONS_LANG_OPTIONS;
|
||||
var PREJOIN;
|
||||
var VIDEO_ENABLED;
|
||||
var AUDIO_ENABLED;
|
||||
|
||||
var SCREENSHARE_BUTTON;
|
||||
var FULLSCREEN_BUTTON;
|
||||
var ACTIVITIES_PANEL_BUTTON;
|
||||
var RECORDING_BUTTON;
|
||||
var BROADCASTING_BUTTON;
|
||||
var CHAT_PANEL_BUTTON;
|
||||
var DISPLAY_LOGO;
|
||||
var DISPLAY_ROOM_NAME;
|
||||
var DISPLAY_PARTICIPANT_NAME;
|
||||
var DISPLAY_AUDIO_DETECTION;
|
||||
var VIDEO_CONTROLS;
|
||||
var LEAVE_BUTTON;
|
||||
var PARTICIPANT_MUTE_BUTTON;
|
||||
var PARTICIPANTS_PANEL_BUTTON;
|
||||
var ACTIVITIES_RECORDING_ACTIVITY;
|
||||
var ACTIVITIES_BROADCASTING_ACTIVITY;
|
||||
var TOOLBAR_SETTINGS_BUTTON;
|
||||
var CAPTIONS_BUTTON;
|
||||
|
||||
var ROOM_NAME;
|
||||
var FAKE_DEVICES;
|
||||
var FAKE_RECORDINGS;
|
||||
|
||||
var PARTICIPANT_NAME;
|
||||
|
||||
var OPENVIDU_CALL_SERVER_URL;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
var url = new URL(window.location.href);
|
||||
|
||||
OPENVIDU_CALL_SERVER_URL = url.searchParams.get('OPENVIDU_CALL_SERVER_URL') || 'http://localhost:6080';
|
||||
|
||||
FAKE_DEVICES = url.searchParams.get('fakeDevices') === null ? false : url.searchParams.get('fakeDevices') === 'true';
|
||||
|
||||
FAKE_RECORDINGS = url.searchParams.get('fakeRecordings') === null ? false : url.searchParams.get('fakeRecordings') === 'true';
|
||||
|
||||
// Directives
|
||||
MINIMAL = url.searchParams.get('minimal') === null ? false : url.searchParams.get('minimal') === 'true';
|
||||
LANG = url.searchParams.get('lang') || 'en';
|
||||
CUSTOM_LANG_OPTIONS = url.searchParams.get('langOptions') === null ? false : url.searchParams.get('langOptions') === 'true';
|
||||
// CAPTIONS_LANG = url.searchParams.get('captionsLang') || 'en-US';
|
||||
// CUSTOM_CAPTIONS_LANG_OPTIONS = url.searchParams.get('captionsLangOptions') === null ? false : url.searchParams.get('captionsLangOptions') === 'true';
|
||||
PARTICIPANT_NAME =
|
||||
url.searchParams.get('participantName') === null
|
||||
? 'TEST_USER' + Math.random().toString(36).substr(2, 9)
|
||||
: url.searchParams.get('participantName');
|
||||
PREJOIN = url.searchParams.get('prejoin') === null ? true : url.searchParams.get('prejoin') === 'true';
|
||||
VIDEO_ENABLED = url.searchParams.get('videoEnabled') === null ? true : url.searchParams.get('videoEnabled') === 'true';
|
||||
AUDIO_ENABLED = url.searchParams.get('audioEnabled') === null ? true : url.searchParams.get('audioEnabled') === 'true';
|
||||
SCREENSHARE_BUTTON = url.searchParams.get('screenshareBtn') === null ? true : url.searchParams.get('screenshareBtn') === 'true';
|
||||
RECORDING_BUTTON =
|
||||
url.searchParams.get('toolbarRecordingButton') === null ? true : url.searchParams.get('toolbarRecordingButton') === 'true';
|
||||
FULLSCREEN_BUTTON = url.searchParams.get('fullscreenBtn') === null ? true : url.searchParams.get('fullscreenBtn') === 'true';
|
||||
BROADCASTING_BUTTON =
|
||||
url.searchParams.get('toolbarBroadcastingButton') === null ? true : url.searchParams.get('toolbarBroadcastingButton') === 'true';
|
||||
|
||||
TOOLBAR_SETTINGS_BUTTON =
|
||||
url.searchParams.get('toolbarSettingsBtn') === null ? true : url.searchParams.get('toolbarSettingsBtn') === 'true';
|
||||
CAPTIONS_BUTTON = url.searchParams.get('toolbarCaptionsBtn') === null ? true : url.searchParams.get('toolbarCaptionsBtn') === 'true';
|
||||
|
||||
LEAVE_BUTTON = url.searchParams.get('leaveBtn') === null ? true : url.searchParams.get('leaveBtn') === 'true';
|
||||
ACTIVITIES_PANEL_BUTTON =
|
||||
url.searchParams.get('activitiesPanelBtn') === null ? true : url.searchParams.get('activitiesPanelBtn') === 'true';
|
||||
CHAT_PANEL_BUTTON = url.searchParams.get('chatPanelBtn') === null ? true : url.searchParams.get('chatPanelBtn') === 'true';
|
||||
PARTICIPANTS_PANEL_BUTTON =
|
||||
url.searchParams.get('participantsPanelBtn') === null ? true : url.searchParams.get('participantsPanelBtn') === 'true';
|
||||
ACTIVITIES_BROADCASTING_ACTIVITY =
|
||||
url.searchParams.get('activitiesPanelBroadcastingActivity') === null
|
||||
? true
|
||||
: url.searchParams.get('activitiesPanelBroadcastingActivity') === 'true';
|
||||
ACTIVITIES_RECORDING_ACTIVITY =
|
||||
url.searchParams.get('activitiesPanelRecordingActivity') === null
|
||||
? true
|
||||
: url.searchParams.get('activitiesPanelRecordingActivity') === 'true';
|
||||
|
||||
DISPLAY_LOGO = url.searchParams.get('displayLogo') === null ? true : url.searchParams.get('displayLogo') === 'true';
|
||||
DISPLAY_ROOM_NAME = url.searchParams.get('displayRoomName') === null ? true : url.searchParams.get('displayRoomName') === 'true';
|
||||
DISPLAY_PARTICIPANT_NAME =
|
||||
url.searchParams.get('displayParticipantName') === null ? true : url.searchParams.get('displayParticipantName') === 'true';
|
||||
DISPLAY_AUDIO_DETECTION =
|
||||
url.searchParams.get('displayAudioDetection') === null ? true : url.searchParams.get('displayAudioDetection') === 'true';
|
||||
VIDEO_CONTROLS = url.searchParams.get('videoControls') === null ? true : url.searchParams.get('videoControls') === 'true';
|
||||
PARTICIPANT_MUTE_BUTTON =
|
||||
url.searchParams.get('participantMuteBtn') === null ? true : url.searchParams.get('participantMuteBtn') === 'true';
|
||||
|
||||
ROOM_NAME = url.searchParams.get('roomName') === null ? `E2ESession${Math.floor(Date.now())}` : url.searchParams.get('roomName');
|
||||
|
||||
var webComponent = document.querySelector('openvidu-webcomponent');
|
||||
|
||||
webComponent.addEventListener('onTokenRequested', (event) => {
|
||||
appendElement('onTokenRequested');
|
||||
console.log('Token ready', event.detail);
|
||||
joinSession(ROOM_NAME, event.detail);
|
||||
});
|
||||
webComponent.addEventListener('onReadyToJoin', (event) => appendElement('onReadyToJoin'));
|
||||
webComponent.addEventListener('onRoomDisconnected', (event) => appendElement('onRoomDisconnected'));
|
||||
webComponent.addEventListener('onVideoEnabledChanged', (event) => appendElement('onVideoEnabledChanged-' + event.detail));
|
||||
webComponent.addEventListener('onVideoDeviceChanged', (event) => appendElement('onVideoDeviceChanged'));
|
||||
webComponent.addEventListener('onAudioEnabledChanged', (eSESSIONvent) => appendElement('onAudioEnabledChanged-' + event.detail));
|
||||
webComponent.addEventListener('onAudioDeviceChanged', (event) => appendElement('onAudioDeviceChanged'));
|
||||
webComponent.addEventListener('onScreenShareEnabledChanged', (event) => appendElement('onScreenShareEnabledChanged'));
|
||||
webComponent.addEventListener('onParticipantsPanelStatusChanged', (event) =>
|
||||
appendElement('onParticipantsPanelStatusChanged-' + event.detail.isOpened)
|
||||
);
|
||||
webComponent.addEventListener('onLangChanged', (event) => appendElement('onLangChanged-' + event.detail.lang));
|
||||
webComponent.addEventListener('onChatPanelStatusChanged', (event) =>
|
||||
appendElement('onChatPanelStatusChanged-' + event.detail.isOpened)
|
||||
);
|
||||
webComponent.addEventListener('onActivitiesPanelStatusChanged', (event) =>
|
||||
appendElement('onActivitiesPanelStatusChanged-' + event.detail.isOpened)
|
||||
);
|
||||
webComponent.addEventListener('onSettingsPanelStatusChanged', (event) =>
|
||||
appendElement('onSettingsPanelStatusChanged-' + event.detail.isOpened)
|
||||
);
|
||||
webComponent.addEventListener('onFullscreenEnabledChanged', (event) => appendElement('onFullscreenEnabledChanged-' + event.detail));
|
||||
|
||||
webComponent.addEventListener('onRecordingStartRequested', async (event) => {
|
||||
appendElement('onRecordingStartRequested-' + event.detail.roomName);
|
||||
// Can't test the recording
|
||||
// RECORDING_ID = await startRecording(SESSION_NAME);
|
||||
});
|
||||
// Can't test the recording
|
||||
// webComponent.addEventListener('onRecordingStopRequested', async (event) => {
|
||||
// appendElement('onRecordingStopRequested-' + event.detail.roomName);
|
||||
// await stopRecording(RECORDING_ID);
|
||||
// });
|
||||
|
||||
webComponent.addEventListener('onRecordingStopRequested', async (event) => {
|
||||
appendElement('onRecordingStopRequested-' + event.detail.roomName);
|
||||
});
|
||||
|
||||
// Can't test the recording
|
||||
// webComponent.addEventListener('onActivitiesPanelStopRecordingClicked', async (event) => {
|
||||
// appendElement('onActivitiesPanelStopRecordingClicked');
|
||||
// await stopRecording(RECORDING_ID);
|
||||
// });
|
||||
|
||||
webComponent.addEventListener('onRecordingDeleteRequested', (event) => {
|
||||
const { roomName, recordingId } = event.detail;
|
||||
appendElement(`onRecordingDeleteRequested-${roomName}-${recordingId}`);
|
||||
});
|
||||
|
||||
webComponent.addEventListener('onBroadcastingStartRequested', async (event) => {
|
||||
const { roomName, broadcastUrl } = event.detail;
|
||||
appendElement(`onBroadcastingStartRequested-${roomName}-${broadcastUrl}`);
|
||||
});
|
||||
|
||||
webComponent.addEventListener('onActivitiesPanelStopBroadcastingClicked', async (event) => {
|
||||
appendElement('onActivitiesPanelStopBroadcastingClicked');
|
||||
});
|
||||
|
||||
webComponent.addEventListener('onRoomCreated', (event) => {
|
||||
var room = event.detail;
|
||||
appendElement('onRoomCreated');
|
||||
|
||||
room.on('disconnected', (e) => {
|
||||
appendElement('roomDisconnected');
|
||||
});
|
||||
});
|
||||
|
||||
webComponent.addEventListener('onParticipantCreated', (event) => {
|
||||
var participant = event.detail;
|
||||
appendElement(`${participant.name}-onParticipantCreated`);
|
||||
});
|
||||
|
||||
setWebcomponentAttributes();
|
||||
});
|
||||
|
||||
function setWebcomponentAttributes() {
|
||||
var webComponent = document.querySelector('openvidu-webcomponent');
|
||||
webComponent.participantName = PARTICIPANT_NAME;
|
||||
|
||||
webComponent.minimal = MINIMAL;
|
||||
webComponent.lang = LANG;
|
||||
if (CUSTOM_LANG_OPTIONS) {
|
||||
webComponent.langOptions = [
|
||||
{ name: 'Esp', lang: 'es' },
|
||||
{ name: 'Eng', lang: 'en' }
|
||||
];
|
||||
}
|
||||
// TODO: Uncomment when the captions are implemented
|
||||
// webComponent.captionsLang = CAPTIONS_LANG;
|
||||
// if (CUSTOM_CAPTIONS_LANG_OPTIONS) {
|
||||
// webComponent.captionsLangOptions = [
|
||||
// { name: 'Esp', lang: 'es-ES' },
|
||||
// { name: 'Eng', lang: 'en-US' }
|
||||
// ];
|
||||
// }
|
||||
if (FAKE_DEVICES) {
|
||||
console.warn('Using fake devices');
|
||||
monkeyPatchMediaDevices();
|
||||
}
|
||||
if (FAKE_RECORDINGS) {
|
||||
console.warn('Using fake recordings');
|
||||
webComponent.recordingActivityRecordingsList = [{ status: 'ready', filename: 'fakeRecording' }];
|
||||
}
|
||||
|
||||
webComponent.prejoin = PREJOIN;
|
||||
webComponent.videoEnabled = VIDEO_ENABLED;
|
||||
webComponent.audioEnabled = AUDIO_ENABLED;
|
||||
webComponent.toolbarScreenshareButton = SCREENSHARE_BUTTON;
|
||||
|
||||
webComponent.toolbarFullscreenButton = FULLSCREEN_BUTTON;
|
||||
webComponent.toolbarSettingsButton = TOOLBAR_SETTINGS_BUTTON;
|
||||
// webComponent.toolbarCaptionsButton = CAPTIONS_BUTTON;
|
||||
webComponent.toolbarLeaveButton = LEAVE_BUTTON;
|
||||
webComponent.toolbarRecordingButton = RECORDING_BUTTON;
|
||||
webComponent.toolbarBroadcastingButton = BROADCASTING_BUTTON;
|
||||
webComponent.toolbarActivitiesPanelButton = ACTIVITIES_PANEL_BUTTON;
|
||||
webComponent.toolbarChatPanelButton = CHAT_PANEL_BUTTON;
|
||||
webComponent.toolbarParticipantsPanelButton = PARTICIPANTS_PANEL_BUTTON;
|
||||
webComponent.toolbarDisplayLogo = DISPLAY_LOGO;
|
||||
webComponent.toolbarDisplayRoomName = DISPLAY_ROOM_NAME;
|
||||
webComponent.streamDisplayParticipantName = DISPLAY_PARTICIPANT_NAME;
|
||||
webComponent.streamDisplayAudioDetection = DISPLAY_AUDIO_DETECTION;
|
||||
webComponent.streamVideoControls = VIDEO_CONTROLS;
|
||||
webComponent.participantPanelItemMuteButton = PARTICIPANT_MUTE_BUTTON;
|
||||
|
||||
webComponent.activitiesPanelRecordingActivity = ACTIVITIES_RECORDING_ACTIVITY;
|
||||
webComponent.activitiesPanelBroadcastingActivity = ACTIVITIES_BROADCASTING_ACTIVITY;
|
||||
}
|
||||
|
||||
function appendElement(id) {
|
||||
var eventsDiv = document.getElementById('events');
|
||||
eventsDiv.setAttribute('style', 'position: absolute;');
|
||||
var element = document.createElement('div');
|
||||
element.setAttribute('id', id);
|
||||
element.setAttribute('style', 'height: 1px;');
|
||||
eventsDiv.appendChild(element);
|
||||
}
|
||||
|
||||
async function joinSession(roomName, participantName) {
|
||||
var webComponent = document.querySelector('openvidu-webcomponent');
|
||||
console.log('Joining session', roomName, participantName);
|
||||
try {
|
||||
webComponent.token = await getToken(roomName, participantName);
|
||||
} catch (error) {
|
||||
webComponent.tokenError = error;
|
||||
}
|
||||
}
|
||||
|
||||
async function getToken(roomName, participantName) {
|
||||
try {
|
||||
const response = await fetch(OPENVIDU_CALL_SERVER_URL + '/call/api/rooms', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
// 'Authorization': 'Basic ' + btoa('OPENVIDUAPP:' + OPENVIDU_SECRET),
|
||||
},
|
||||
body: JSON.stringify({
|
||||
participantName,
|
||||
roomName
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch token');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.token;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>openvidu-web-component</title>
|
||||
<script type="module" src="utils/filter-stream.js"></script>
|
||||
<!-- <script type="module" src="utils/shader-renderer.js"></script> -->
|
||||
<script type="module" src="utils/media-devices.js"></script>
|
||||
<script type="module" src="app.js"></script>
|
||||
<script src="openvidu-webcomponent-dev.js"></script>
|
||||
<link rel="stylesheet" href="openvidu-webcomponent-dev.css" />
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--ov-background-color: #303030;
|
||||
--ov-secondary-action-color: #3e3f3f;
|
||||
--ov-accent-action-color: #598eff;
|
||||
--ov-error-color: #eb5144;
|
||||
--ov-accent-action-color: #ffae35;
|
||||
--ov-light-color: #e6e6e6;
|
||||
|
||||
--ov-secondary-action-color: #3a3d3d;
|
||||
--ov-text-primary-color: #ffffff;
|
||||
|
||||
--ov-text-primary-color: #1d1d1d;
|
||||
--ov-surface-color: #ffffff;
|
||||
|
||||
--ov-toolbar-buttons-radius: 50%;
|
||||
--ov-leave-button-radius: 10px;
|
||||
--ov-video-radius: 5px;
|
||||
--ov-surface-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="events"></div>
|
||||
<!-- OpenVidu Web Component -->
|
||||
<openvidu-webcomponent></openvidu-webcomponent>
|
||||
</body>
|
||||
</html>
|
|
@ -1,21 +1,21 @@
|
|||
// Ideally we'd use an editor or import shaders directly from the API.
|
||||
import { FilterStream } from './filter-stream.js';
|
||||
|
||||
export const monkeyPatchMediaDevices = () => {
|
||||
export default function monkeyPatchMediaDevices() {
|
||||
const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices;
|
||||
const getUserMediaFn = MediaDevices.prototype.getUserMedia;
|
||||
const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia;
|
||||
|
||||
const fakeVideoDevice = {
|
||||
deviceId: 'virtual_video',
|
||||
groupId: '',
|
||||
deviceId: 'virtual',
|
||||
groupID: '',
|
||||
kind: 'videoinput',
|
||||
label: 'custom_fake_video_1'
|
||||
};
|
||||
|
||||
const fakeAudioDevice = {
|
||||
deviceId: 'virtual_audio',
|
||||
groupId: '',
|
||||
deviceId: 'virtual',
|
||||
groupID: '',
|
||||
kind: 'audioinput',
|
||||
label: 'custom_fake_audio_1'
|
||||
};
|
||||
|
@ -29,21 +29,8 @@ export const monkeyPatchMediaDevices = () => {
|
|||
|
||||
const getUserMediaMonkeyPatch = async function () {
|
||||
const args = arguments[0];
|
||||
|
||||
if (args.audio && (args.audio.deviceId === 'virtual_audio' || args.audio.deviceId?.exact === 'virtual_audio')) {
|
||||
const constraints = {
|
||||
audio: {
|
||||
facingMode: args.facingMode,
|
||||
advanced: args.audio.advanced,
|
||||
deviceId: fakeAudioDevice.deviceId
|
||||
},
|
||||
video: false
|
||||
};
|
||||
const res = await getUserMediaFn.call(navigator.mediaDevices, constraints);
|
||||
return res;
|
||||
} else if (args.video && (args.video.deviceId === 'virtual_video' || args.video.deviceId?.exact === 'virtual_video')) {
|
||||
const { deviceId, advanced, width, height } = args.video;
|
||||
|
||||
if (deviceId === 'virtual' || deviceId?.exact === 'virtual') {
|
||||
const constraints = {
|
||||
video: {
|
||||
facingMode: args.facingMode,
|
|
@ -1,35 +1,29 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
let url = '';
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Testing API Directives', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
browser = await createChromeBrowser();
|
||||
utils = new OpenViduComponentsPO(browser);
|
||||
url = `${TestAppConfig.appUrl}&roomName=API_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
try {
|
||||
if (await utils.isPresent('#session-container')) {
|
||||
await utils.leaveRoom();
|
||||
}
|
||||
} catch (error) {}
|
||||
await browser.sleep(500);
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
|
@ -174,7 +168,7 @@ describe('Testing API Directives', () => {
|
|||
});
|
||||
|
||||
it('should show the token error WITH prejoin page', async () => {
|
||||
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TEST_TOKEN&participantName=PNAME`;
|
||||
const fixedUrl = `${url}&roomName=TEST_TOKEN&participantName=PNAME`;
|
||||
await browser.get(`${fixedUrl}`);
|
||||
|
||||
// Checking if prejoin page exist
|
||||
|
@ -204,7 +198,7 @@ describe('Testing API Directives', () => {
|
|||
});
|
||||
|
||||
it('should show the token error WITHOUT prejoin page', async () => {
|
||||
const fixedUrl = `${TestAppConfig.appUrl}&roomName=TOKEN_ERROR&prejoin=false&participantName=PNAME`;
|
||||
const fixedUrl = `${url}&roomName=TOKEN_ERROR&prejoin=false&participantName=PNAME`;
|
||||
await browser.get(`${fixedUrl}`);
|
||||
|
||||
// Checking if session container is present
|
||||
|
@ -238,11 +232,11 @@ describe('Testing API Directives', () => {
|
|||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.waitForElement('#videocam_off');
|
||||
expect(await utils.isPresent('#videocam_off')).toBeTrue();
|
||||
|
||||
await utils.waitForElement('#video-poster');
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(0);
|
||||
|
||||
await utils.waitForElement('#videocam_off');
|
||||
expect(await utils.isPresent('#videocam_off')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should run the app with VIDEO DISABLED and WITHOUT PREJOIN page', async () => {
|
||||
|
@ -287,7 +281,6 @@ describe('Testing API Directives', () => {
|
|||
it('should run the app with AUDIO DISABLED and WITHOUT PREJOIN page', async () => {
|
||||
await browser.get(`${url}&prejoin=false&audioEnabled=false`);
|
||||
|
||||
await browser.sleep(1000);
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
// Checking if video is displayed
|
||||
|
@ -299,30 +292,6 @@ describe('Testing API Directives', () => {
|
|||
expect(await utils.isPresent('#mic_off')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should run the app without camera button', async () => {
|
||||
await browser.get(`${url}&prejoin=false&cameraBtn=false`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
// Checking if toolbar is present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Checking if camera button is not present
|
||||
expect(await utils.isPresent('#camera-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should run the app without microphone button', async () => {
|
||||
await browser.get(`${url}&prejoin=falseµphoneBtn=false`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
// Checking if toolbar is present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Checking if microphone button is not present
|
||||
expect(await utils.isPresent('#microphone-btn')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should HIDE the SCREENSHARE button', async () => {
|
||||
await browser.get(`${url}&prejoin=false&screenshareBtn=false`);
|
||||
|
||||
|
@ -539,7 +508,7 @@ describe('Testing API Directives', () => {
|
|||
|
||||
it('should HIDE the MUTE button in participants panel', async () => {
|
||||
const roomName = 'e2etest';
|
||||
const fixedUrl = `${TestAppConfig.appUrl}&prejoin=false&participantMuteBtn=false&roomName=${roomName}`;
|
||||
const fixedUrl = `${url}&prejoin=false&participantMuteBtn=false&roomName=${roomName}`;
|
||||
await browser.get(fixedUrl);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
@ -558,9 +527,8 @@ describe('Testing API Directives', () => {
|
|||
expect(await utils.isPresent('#remote-participant-item')).toBeFalse();
|
||||
|
||||
// Starting new browser for adding a new participant
|
||||
const newTabScript = `window.open("${fixedUrl}&participantName=SecondParticipant")`;
|
||||
const newTabScript = `window.open("${fixedUrl}")`;
|
||||
await browser.executeScript(newTabScript);
|
||||
await browser.sleep(10000);
|
||||
|
||||
// Go to first tab
|
||||
const tabs = await browser.getAllWindowHandles();
|
|
@ -1,8 +1,9 @@
|
|||
import { Builder, Key, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
//TODO: Uncomment when captions are implemented
|
||||
// describe('Testing captions features', () => {
|
||||
|
@ -10,10 +11,10 @@ const url = TestAppConfig.appUrl;
|
|||
// let utils: OpenViduComponentsPO;
|
||||
// async function createChromeBrowser(): Promise<WebDriver> {
|
||||
// return await new Builder()
|
||||
// .forBrowser(TestAppConfig.browserName)
|
||||
// .withCapabilities(TestAppConfig.browserCapabilities)
|
||||
// .setChromeOptions(TestAppConfig.browserOptions)
|
||||
// .usingServer(TestAppConfig.seleniumAddress)
|
||||
// .forBrowser(WebComponentConfig.browserName)
|
||||
// .withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
// .setChromeOptions(WebComponentConfig.browserOptions)
|
||||
// .usingServer(WebComponentConfig.seleniumAddress)
|
||||
// .build();
|
||||
// }
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Testing CHAT features', () => {
|
||||
let browser: WebDriver;
|
||||
|
@ -10,10 +11,10 @@ describe('Testing CHAT features', () => {
|
|||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -23,10 +24,6 @@ describe('Testing CHAT features', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
// leaving room if connected
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
|
@ -1,19 +1,20 @@
|
|||
import { Builder, Key, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Testing videoconference EVENTS', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
const isHeadless: boolean = (TestAppConfig.browserOptions as any).options_.args.includes('--headless');
|
||||
const isHeadless: boolean = (WebComponentConfig.browserOptions as any).options_.args.includes('--headless');
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -23,10 +24,6 @@ describe('Testing videoconference EVENTS', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
// leaving room if connected
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
|
@ -60,6 +57,23 @@ describe('Testing videoconference EVENTS', () => {
|
|||
expect(await utils.isPresent('#onTokenRequested')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should receive the onRoomDisconnected event', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Clicking to leave button
|
||||
const leaveButton = await utils.waitForElement('#leave-btn');
|
||||
expect(await utils.isPresent('#leave-btn')).toBeTrue();
|
||||
await leaveButton.click();
|
||||
|
||||
// Checking if onRoomDisconnected has been received
|
||||
await utils.waitForElement('#onRoomDisconnected');
|
||||
expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should receive the onVideoEnabledChanged event when clicking on the prejoin', async () => {
|
||||
await browser.get(url);
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -599,7 +613,7 @@ describe('Testing videoconference EVENTS', () => {
|
|||
expect(await utils.isPresent('#onReadyToJoin')).toBeFalse();
|
||||
});
|
||||
|
||||
// PARTICIPANT EVENTS
|
||||
// * PUBLISHER EVENTS
|
||||
|
||||
it('should receive onParticipantCreated event from LOCAL participant', async () => {
|
||||
const participantName = 'TEST_USER';
|
||||
|
@ -608,39 +622,22 @@ describe('Testing videoconference EVENTS', () => {
|
|||
expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should receive the onParticipantLeft event', async () => {
|
||||
await browser.get(`${url}&prejoin=false&redirectToHome=false`);
|
||||
// * ROOM EVENTS
|
||||
|
||||
it('should receive roomDisconnected event from LOCAL participant', async () => {
|
||||
const participantName = 'TEST_USER';
|
||||
let element;
|
||||
await browser.get(`${url}&prejoin=false&participantName=${participantName}`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Clicking to leave button
|
||||
const leaveButton = await utils.waitForElement('#leave-btn');
|
||||
expect(await utils.isPresent('#leave-btn')).toBeTrue();
|
||||
await leaveButton.click();
|
||||
// Checking if leave button is not present
|
||||
element = await utils.waitForElement('#leave-btn');
|
||||
await element.click();
|
||||
|
||||
await utils.waitForElement('#events');
|
||||
// Checking if onParticipantLeft has been received
|
||||
await utils.waitForElement('#onParticipantLeft');
|
||||
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
|
||||
await utils.waitForElement(`#roomDisconnected`);
|
||||
expect(await utils.isPresent(`#roomDisconnected`)).toBeTrue();
|
||||
});
|
||||
|
||||
// * ROOM EVENTS
|
||||
|
||||
//TODO: Implement a mechanism to emulate network disconnection
|
||||
// it('should receive the onRoomDisconnected event', async () => {
|
||||
// await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
// await utils.checkSessionIsPresent();
|
||||
|
||||
// await utils.checkToolbarIsPresent();
|
||||
|
||||
// // Emulate network disconnection
|
||||
// await utils.forceCloseWebsocket();
|
||||
|
||||
// // Checking if onRoomDisconnected has been received
|
||||
// await utils.waitForElement('#onRoomDisconnected');
|
||||
// expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
|
||||
// });
|
||||
});
|
|
@ -1,18 +1,19 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { getBrowserOptionsWithoutDevices, TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Media Devices: Virtual Device Replacement and Permissions Handling', () => {
|
||||
describe('Testing replace track with emulated devices', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -22,99 +23,118 @@ describe('Media Devices: Virtual Device Replacement and Permissions Handling', (
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
// console.log('data:image/png;base64,' + await browser.takeScreenshot());
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should allow selecting and replacing the video track with a custom virtual device in the prejoin page', async () => {
|
||||
it('should replace the video track in prejoin page', async () => {
|
||||
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
|
||||
|
||||
await browser.get(`${url}&fakeDevices=true`);
|
||||
|
||||
let videoDevices = await utils.waitForElement('#video-devices-form');
|
||||
|
||||
await videoDevices.click();
|
||||
|
||||
let element = await utils.waitForElement('#option-custom_fake_video_1');
|
||||
|
||||
await element.click();
|
||||
|
||||
let videoLabel;
|
||||
|
||||
await browser.sleep(1000);
|
||||
videoLabel = await browser.executeScript<string>(script);
|
||||
expect(videoLabel).toEqual('custom_fake_video_1');
|
||||
|
||||
await videoDevices.click();
|
||||
|
||||
element = await utils.waitForElement('#option-fake_device_0');
|
||||
await element.click();
|
||||
|
||||
await browser.sleep(1000);
|
||||
videoLabel = await browser.executeScript<string>(script);
|
||||
expect(videoLabel).toEqual('fake_device_0');
|
||||
});
|
||||
|
||||
it('should allow selecting and replacing the video track with a custom virtual device in the videoconference page', async () => {
|
||||
it('should replace the video track in videoconference page', async () => {
|
||||
const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
|
||||
|
||||
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
// Checking if toolbar is present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.togglePanel('settings');
|
||||
|
||||
await utils.waitForElement('.settings-container');
|
||||
expect(await utils.isPresent('.settings-container')).toBeTrue();
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.clickOn('#video-opt');
|
||||
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
|
||||
|
||||
let videoDevices = await utils.waitForElement('#video-devices-form');
|
||||
|
||||
await videoDevices.click();
|
||||
|
||||
let element = await utils.waitForElement('#option-custom_fake_video_1');
|
||||
|
||||
await element.click();
|
||||
|
||||
let videoLabel;
|
||||
await browser.sleep(1000);
|
||||
videoLabel = await browser.executeScript<string>(script);
|
||||
expect(videoLabel).toEqual('custom_fake_video_1');
|
||||
|
||||
await videoDevices.click();
|
||||
|
||||
element = await utils.waitForElement('#option-fake_device_0');
|
||||
await element.click();
|
||||
|
||||
await browser.sleep(1000);
|
||||
videoLabel = await browser.executeScript<string>(script);
|
||||
expect(videoLabel).toEqual('fake_device_0');
|
||||
});
|
||||
|
||||
it('should replace the screen track with a custom virtual device', async () => {
|
||||
const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
|
||||
// TODO: Uncommented when Livekit allows to replace the screen track
|
||||
// it('should replace the screen track', async () => {
|
||||
// const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;';
|
||||
|
||||
await browser.get(`${url}&prejoin=false&fakeDevices=true`);
|
||||
// await browser.get(`${url}&prejoin=false&fakeDevices=true`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
await utils.checkToolbarIsPresent();
|
||||
// await utils.checkLayoutPresent();
|
||||
// await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
// await utils.clickOn('#screenshare-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
// await browser.sleep(500);
|
||||
|
||||
let screenLabel = await browser.executeScript<string>(script);
|
||||
expect(screenLabel).not.toEqual('custom_fake_screen');
|
||||
// let screenLabel = await browser.executeScript<string>(script);
|
||||
// expect(screenLabel).not.toEqual('custom_fake_screen');
|
||||
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
// await utils.clickOn('#video-settings-btn-SCREEN');
|
||||
// await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('#replace-screen-button');
|
||||
await utils.clickOn('#replace-screen-button');
|
||||
await browser.sleep(1000);
|
||||
// await utils.waitForElement('.video-settings-menu');
|
||||
// const replaceBtn = await utils.waitForElement('#replace-screen-button');
|
||||
// await replaceBtn.sendKeys(Key.ENTER);
|
||||
|
||||
screenLabel = await browser.executeScript<string>(script);
|
||||
expect(screenLabel).toEqual('custom_fake_screen');
|
||||
});
|
||||
// await browser.sleep(1000);
|
||||
// screenLabel = await browser.executeScript<string>(script);
|
||||
// expect(screenLabel).to.be.toEqual('custom_fake_screen');
|
||||
// });
|
||||
});
|
||||
|
||||
describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
|
||||
describe('Testing WITHOUT MEDIA DEVICES permissions', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(getBrowserOptionsWithoutDevices())
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -124,56 +144,74 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should disable camera and microphone buttons in the prejoin page when permissions are denied', async () => {
|
||||
it('should be able to ACCESS to PREJOIN page', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
||||
let button = await utils.waitForElement('#camera-button');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
|
||||
button = await utils.waitForElement('#microphone-button');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should disable camera and microphone buttons in the room page when permissions are denied', async () => {
|
||||
it('should be able to ACCESS to ROOM page', async () => {
|
||||
await browser.get(`${url}`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
||||
await utils.clickOn('#join-button');
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
let button = await utils.waitForElement('#camera-btn');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
|
||||
button = await utils.waitForElement('#mic-btn');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should disable camera and microphone buttons in the room page without prejoin when permissions are denied', async () => {
|
||||
it('should be able to ACCESS to ROOM page without prejoin', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
let button = await utils.waitForElement('#camera-btn');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
|
||||
button = await utils.waitForElement('#mic-btn');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
});
|
||||
|
||||
it('should disable camera and microphone device selection buttons in settings when permissions are denied', async () => {
|
||||
it('should the settings buttons be disabled', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.togglePanel('settings');
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('.settings-container');
|
||||
expect(await utils.isPresent('.settings-container')).toBeTrue();
|
||||
|
||||
await utils.clickOn('#video-opt');
|
||||
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
|
||||
|
||||
let button = await utils.waitForElement('#camera-button');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
|
||||
await utils.clickOn('#audio-opt');
|
||||
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
|
||||
|
||||
button = await utils.waitForElement('#microphone-button');
|
||||
expect(await button.isEnabled()).toBeFalse();
|
||||
});
|
|
@ -1,19 +1,20 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { Builder, Key, WebDriver } from 'selenium-webdriver';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { getBrowserOptionsWithoutDevices, WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Panels: UI Navigation and Section Switching', () => {
|
||||
describe('Testing panels', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -23,9 +24,6 @@ describe('Panels: UI Navigation and Section Switching', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
|
@ -63,104 +61,164 @@ describe('Panels: UI Navigation and Section Switching', () => {
|
|||
// expect(await utils.isPresent('#background-effects-container')).toBeFalse();
|
||||
// });
|
||||
|
||||
it('should open and close the CHAT panel and verify its content', async () => {
|
||||
it('should toggle CHAT panel', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
const chatButton = await utils.waitForElement('#chat-panel-btn');
|
||||
await chatButton.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
await utils.waitForElement('.input-container');
|
||||
expect(await utils.isPresent('.input-container')).toBeTrue();
|
||||
|
||||
await utils.waitForElement('.messages-container');
|
||||
expect(await utils.isPresent('.messages-container')).toBeTrue();
|
||||
|
||||
await chatButton.click();
|
||||
|
||||
expect(await utils.isPresent('.input-container')).toBeFalse();
|
||||
expect(await utils.isPresent('.messages-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should open and close the PARTICIPANTS panel and verify its content', async () => {
|
||||
it('should toggle PARTICIPANTS panel', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
const participantBtn = await utils.waitForElement('#participants-panel-btn');
|
||||
await participantBtn.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
|
||||
await utils.waitForElement('.local-participant-container');
|
||||
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
|
||||
|
||||
await utils.waitForElement('ov-participant-panel-item');
|
||||
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
|
||||
|
||||
await participantBtn.click();
|
||||
|
||||
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
|
||||
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should open and close the ACTIVITIES panel and verify its content', async () => {
|
||||
it('should toggle ACTIVITIES panel', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Get activities button and click into it
|
||||
const activitiesBtn = await utils.waitForElement('#activities-panel-btn');
|
||||
await activitiesBtn.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
await utils.waitForElement('#activities-container');
|
||||
expect(await utils.isPresent('#activities-container')).toBeTrue();
|
||||
|
||||
await utils.waitForElement('#recording-activity');
|
||||
expect(await utils.isPresent('#recording-activity')).toBeTrue();
|
||||
await activitiesBtn.click();
|
||||
|
||||
expect(await utils.isPresent('#activities-container')).toBeFalse();
|
||||
expect(await utils.isPresent('#recording-activity')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should open the SETTINGS panel and verify its content', async () => {
|
||||
it('should toggle SETTINGS panel', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Checking if toolbar is present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
await utils.togglePanel('settings');
|
||||
|
||||
element = await utils.waitForElement('.sidenav-menu');
|
||||
expect(await utils.isPresent('#default-settings-panel')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should switch between PARTICIPANTS and CHAT panels and verify correct content is shown', async () => {
|
||||
it('should switching between PARTICIPANTS and CHAT panels', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkSessionIsPresent();
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open chat panel
|
||||
const chatButton = await utils.waitForElement('#chat-panel-btn');
|
||||
await chatButton.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
|
||||
|
||||
await utils.waitForElement('.input-container');
|
||||
expect(await utils.isPresent('.input-container')).toBeTrue();
|
||||
|
||||
expect(await utils.isPresent('.messages-container')).toBeTrue();
|
||||
|
||||
// Open participants panel
|
||||
const participantBtn = await utils.waitForElement('#participants-panel-btn');
|
||||
await participantBtn.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
|
||||
expect(await utils.isPresent('.local-participant-container')).toBeTrue();
|
||||
|
||||
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
|
||||
|
||||
// Switch to chat panel
|
||||
await chatButton.click();
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
|
||||
expect(await utils.isPresent('.input-container')).toBeTrue();
|
||||
|
||||
expect(await utils.isPresent('.messages-container')).toBeTrue();
|
||||
|
||||
expect(await utils.isPresent('.local-participant-container')).toBeFalse();
|
||||
|
||||
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
|
||||
|
||||
// Close chat panel
|
||||
await chatButton.click();
|
||||
expect(await utils.getNumberOfElements('.input-container')).toEqual(0);
|
||||
expect(await utils.isPresent('messages-container')).toBeFalse();
|
||||
});
|
||||
|
||||
it('should switch between sections in the SETTINGS panel and verify correct content is shown', async () => {
|
||||
it('should switching between sections in SETTINGS PANEL', async () => {
|
||||
let element;
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Checking if toolbar is present
|
||||
await utils.checkToolbarIsPresent();
|
||||
|
||||
// Open more options menu
|
||||
await utils.togglePanel('settings');
|
||||
|
||||
await utils.waitForElement('.sidenav-menu');
|
||||
expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
|
||||
await browser.sleep(500);
|
||||
|
||||
// Check if general section is shown
|
||||
element = await utils.waitForElement('#general-opt');
|
||||
await element.click();
|
||||
|
||||
expect(await utils.isPresent('ov-participant-name-input')).toBeTrue();
|
||||
|
||||
// Check if video section is shown
|
||||
element = await utils.waitForElement('#video-opt');
|
||||
await element.click();
|
||||
|
||||
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
|
||||
|
||||
// Check if audio section is shown
|
||||
element = await utils.waitForElement('#audio-opt');
|
||||
await element.click();
|
||||
|
||||
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
|
||||
});
|
||||
});
|
|
@ -1,19 +1,19 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('E2E: Screensharing features', () => {
|
||||
describe('Testing screenshare features', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -23,89 +23,95 @@ describe('E2E: Screensharing features', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should toggle screensharing on and off twice, updating video count', async () => {
|
||||
it('should toggle screensharing twice', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Enable screensharing
|
||||
// Clicking to screensharing button
|
||||
await utils.waitForElement('#screenshare-btn');
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
|
||||
await browser.sleep(500);
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(2);
|
||||
|
||||
// Disable screensharing
|
||||
// expect(await utils.getNumberOfElements('.OV_stream.speaking')).toEqual(1);
|
||||
|
||||
await utils.disableScreenShare();
|
||||
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(1);
|
||||
|
||||
// Enable again
|
||||
// toggle screenshare again
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(2);
|
||||
|
||||
// Disable again
|
||||
await utils.disableScreenShare();
|
||||
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should show screenshare and muted camera (camera off, screenshare on)', async () => {
|
||||
it('should show screen and muted camera', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Mute camera
|
||||
await utils.waitForElement('#camera-btn');
|
||||
await utils.clickOn('#camera-btn');
|
||||
|
||||
// Enable screensharing
|
||||
// Clicking to screensharing button
|
||||
const screenshareButton = await utils.waitForElement('#screenshare-btn');
|
||||
expect(await screenshareButton.isDisplayed()).toBeTrue();
|
||||
await screenshareButton.click();
|
||||
|
||||
await browser.sleep(500);
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(2);
|
||||
|
||||
// Disable screensharing
|
||||
await utils.disableScreenShare();
|
||||
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should display screensharing with a single pinned video', async () => {
|
||||
it('should screensharing with PINNED video', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Enable screensharing
|
||||
// Clicking to screensharing button
|
||||
const screenshareButton = await utils.waitForElement('#screenshare-btn');
|
||||
expect(await screenshareButton.isDisplayed()).toBeTrue();
|
||||
await screenshareButton.click();
|
||||
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should replace pinned video when a second participant starts screensharing', async () => {
|
||||
it('should screensharing with PINNED video and replace the existing one', async () => {
|
||||
const roomName = 'screensharingE2E';
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
|
||||
await browser.get(fixedUrl);
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// First participant screenshares
|
||||
// Clicking to screensharing button
|
||||
await utils.waitForElement('#screenshare-btn');
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
|
||||
// Second participant joins and screenshares
|
||||
// Starting new browser for adding the second participant
|
||||
const newTabScript = `window.open("${fixedUrl}")`;
|
||||
await browser.executeScript(newTabScript);
|
||||
const tabs = await browser.getAllWindowHandles();
|
||||
await browser.switchTo().window(tabs[1]);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Clicking to screensharing button
|
||||
await utils.waitForElement('#screenshare-btn');
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
|
@ -113,7 +119,7 @@ describe('E2E: Screensharing features', () => {
|
|||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
|
||||
// Switch back to first tab and check
|
||||
// Go to first tab
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
await browser.sleep(500);
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(4);
|
||||
|
@ -121,37 +127,39 @@ describe('E2E: Screensharing features', () => {
|
|||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should unpin screensharing and restore previous pinned video when disabled', async () => {
|
||||
it('should disabled a screensharing and pinned the previous one', async () => {
|
||||
const roomName = 'screensharingtwoE2E';
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
|
||||
await browser.get(fixedUrl);
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// First participant screenshares
|
||||
// Clicking to screensharing button
|
||||
await utils.waitForElement('#screenshare-btn');
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
|
||||
// Second participant joins and screenshares
|
||||
// Starting new browser for adding the second participant
|
||||
const tabs = await utils.openTab(fixedUrl);
|
||||
await browser.switchTo().window(tabs[1]);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
||||
// Clicking to screensharing button
|
||||
await utils.waitForElement('#screenshare-btn');
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(4);
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
|
||||
// Disable screensharing for second participant
|
||||
// Disable screensharing
|
||||
await utils.disableScreenShare();
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(3);
|
||||
await utils.waitForElement('.OV_big');
|
||||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
|
||||
// Switch back to first tab and check
|
||||
// Go to first tab
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
await browser.sleep(500);
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(3);
|
||||
|
@ -159,52 +167,38 @@ describe('E2E: Screensharing features', () => {
|
|||
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should correctly share screen with microphone muted and maintain proper track state', async () => {
|
||||
// Helper for inspecting stream tracks
|
||||
const getMediaTracks = (className: string) => {
|
||||
return `
|
||||
const tracks = document.getElementsByClassName('${className}')[0].srcObject.getTracks();
|
||||
return tracks.map(track => ({
|
||||
kind: track.kind,
|
||||
enabled: track.enabled,
|
||||
id: track.id,
|
||||
label: track.label
|
||||
}));`;
|
||||
};
|
||||
// it('should screensharing with audio muted', async () => {
|
||||
// let isAudioEnabled;
|
||||
// const getAudioScript = (className: string) => {
|
||||
// return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
|
||||
// };
|
||||
// await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
// Setup: Navigate to room and skip prejoin
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
await utils.checkLayoutPresent();
|
||||
// await utils.checkLayoutPresent();
|
||||
|
||||
// Step 1: First mute the microphone
|
||||
const micButton = await utils.waitForElement('#mic-btn');
|
||||
await micButton.click();
|
||||
// const micButton = await utils.waitForElement('#mic-btn');
|
||||
// await micButton.click();
|
||||
|
||||
// Step 2: Start screen sharing
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
// // Clicking to screensharing button
|
||||
// const screenshareButton = await utils.waitForElement('#screenshare-btn');
|
||||
// expect(await utils.isPresent('#screenshare-btn')).toBeTrue();
|
||||
// await screenshareButton.click();
|
||||
|
||||
// Step 3: Verify both streams are present
|
||||
await utils.waitForElement('.screen-type');
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(2);
|
||||
// await utils.waitForElement('.screen-type');
|
||||
// expect(await utils.getNumberOfElements('video')).toEqual(2);
|
||||
|
||||
// Step 4: Verify screen share track properties
|
||||
const screenTracks: any[] = await browser.executeScript(getMediaTracks('screen-type'));
|
||||
expect(screenTracks.length).toEqual(1);
|
||||
expect(screenTracks[0].kind).toEqual('video');
|
||||
expect(screenTracks[0].enabled).toBeTrue();
|
||||
// isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
|
||||
// expect(isAudioEnabled).toBeFalse();
|
||||
|
||||
// Step 5: Verify microphone status indicators for both streams
|
||||
// await utils.waitForElement('#status-mic');
|
||||
// const micStatusCount = await utils.getNumberOfElements('#status-mic');
|
||||
// expect(micStatusCount).toEqual(2);
|
||||
// expect(await utils.getNumberOfElements('#status-mic')).toEqual(2);
|
||||
|
||||
// // Clicking to screensharing button
|
||||
// await screenshareButton.click();
|
||||
// expect(await utils.getNumberOfElements('video')).toEqual(1);
|
||||
|
||||
// });
|
||||
|
||||
// Step 6: Stop screen sharing and verify stream count
|
||||
await utils.clickOn('#screenshare-btn');
|
||||
await browser.sleep(500);
|
||||
await utils.clickOn('#disable-screen-button');
|
||||
await browser.sleep(500);
|
||||
expect(await utils.getNumberOfElements('video')).toEqual(1);
|
||||
});
|
||||
// it('should show and hide CAMERA stream when muting video with screensharing', async () => {
|
||||
// await browser.get(`${url}&prejoin=false`);
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
import { Builder, ILocation, IRectangle, ISize, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Stream rendering and media toggling scenarios', () => {
|
||||
describe('Checking stream elements by disabling/enabling the media', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -22,13 +23,10 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should not render any video element when joining with video disabled', async () => {
|
||||
it('should show 0 video element when a participant joins with video disabled', async () => {
|
||||
await browser.get(`${url}&prejoin=true&videoEnabled=false`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -41,7 +39,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should render a video element but no audio when joining with audio muted', async () => {
|
||||
it('should show a video element when a participant joins with audio muted', async () => {
|
||||
await browser.get(`${url}&prejoin=true&audioEnabled=false`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -54,7 +52,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should render both video and audio elements when joining with both enabled', async () => {
|
||||
it('should show a video element when a participant joins', async () => {
|
||||
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -67,7 +65,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(1);
|
||||
});
|
||||
|
||||
it('should add a screen share video/audio when sharing screen with both camera and mic muted', async () => {
|
||||
it('should show a video element when a participant shares its screen with VIDEO and AUDIO MUTED', async () => {
|
||||
await browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=false`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -92,7 +90,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add a screen share video/audio when sharing screen with both camera and mic enabled', async () => {
|
||||
it('should show a video element when a LOCAL participant shares its screen', async () => {
|
||||
await browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
|
||||
|
||||
await utils.checkPrejoinIsPresent();
|
||||
|
@ -117,9 +115,9 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(1);
|
||||
});
|
||||
|
||||
/* ------------ Checking video/audio elements with two participants ------------ */
|
||||
/* ------------ Checking video elements with two participants ------------ */
|
||||
|
||||
it('should not render any video/audio elements when two participants join with both video and audio muted', async () => {
|
||||
it('should show zero video elements when two participants join with VIDEO and AUDIO MUTED', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -147,7 +145,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should render two video elements and no audio when two participants join with audio muted', async () => {
|
||||
it('should show two video elements when a two participants join with audio muted', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -160,8 +158,6 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
|
||||
const tabs = await utils.openTab(fixedUrl);
|
||||
await browser.sleep(1000);
|
||||
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
|
||||
await utils.waitForElement('.OV_stream.remote');
|
||||
|
@ -177,7 +173,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should not render any video elements but should render two audio elements when two participants join with video disabled', async () => {
|
||||
it('should show zero video elements when two participants join with video disabled', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -190,7 +186,6 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(1);
|
||||
|
||||
const tabs = await utils.openTab(fixedUrl);
|
||||
await browser.sleep(1000);
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
|
||||
await utils.waitForElement('.OV_stream.remote');
|
||||
|
@ -206,7 +201,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(2);
|
||||
});
|
||||
|
||||
it('should add a screen share video/audio when a participant with both video and audio muted shares their screen (two participants)', async () => {
|
||||
it('should show 3 video elements when a participant shares its screen with AUDIO and VIDEO MUTED', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -245,7 +240,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(0);
|
||||
});
|
||||
|
||||
it('should add a screen share video/audio when a remote participant with both video and audio enabled shares their screen', async () => {
|
||||
it('should show 3 video elements when a REMOTE participant shares its screen', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -284,7 +279,7 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
expect(await utils.getNumberOfElements('audio')).toEqual(2);
|
||||
});
|
||||
|
||||
it('should add a screen share video/audio for both participants when both share their screen with video/audio muted', async () => {
|
||||
it('should show 4 video elements when a two participants share theirs screen', async () => {
|
||||
const roomName = `streams-${Date.now()}`;
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
|
||||
await browser.get(fixedUrl);
|
||||
|
@ -328,15 +323,15 @@ describe('Stream rendering and media toggling scenarios', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Stream UI controls and interaction features', () => {
|
||||
describe('Testing stream features', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -346,9 +341,6 @@ describe('Stream UI controls and interaction features', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
|
@ -666,7 +658,7 @@ describe('Stream UI controls and interaction features', () => {
|
|||
expect(streamProps.y).toEqual(0);
|
||||
});
|
||||
|
||||
it('should show the audio detection elements when participant is speaking', async () => {
|
||||
xit('should show the audio detection elements when participant is speaking', async () => {
|
||||
const roomName = 'speakingE2E';
|
||||
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
|
||||
await browser.get(`${fixedUrl}&audioEnabled=false`);
|
||||
|
@ -676,30 +668,25 @@ describe('Stream UI controls and interaction features', () => {
|
|||
// Starting new browser for adding the second participant
|
||||
const newTabScript = `window.open("${fixedUrl}")`;
|
||||
await browser.executeScript(newTabScript);
|
||||
await browser.sleep(1000);
|
||||
const tabs = await browser.getAllWindowHandles();
|
||||
|
||||
await browser.switchTo().window(tabs[0]);
|
||||
|
||||
await utils.waitForElement('.OV_stream.remote.speaking');
|
||||
expect(await utils.getNumberOfElements('.OV_stream.remote.speaking')).toEqual(1);
|
||||
|
||||
// Check only one element is marked as speaker due to the local participant is muted
|
||||
await utils.waitForElement('.OV_stream.speaking');
|
||||
expect(await utils.getNumberOfElements('.OV_stream.speaking')).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Video playback reliability with different media states', () => {
|
||||
describe('Testing video is playing', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -709,9 +696,6 @@ describe('Video playback reliability with different media states', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
import { Builder, WebDriver } from 'selenium-webdriver';
|
||||
import { TestAppConfig } from './selenium.conf';
|
||||
import { OpenViduComponentsPO } from './utils.po.test';
|
||||
import { OPENVIDU_CALL_SERVER } from '../config';
|
||||
import { WebComponentConfig } from '../selenium.conf';
|
||||
import { OpenViduComponentsPO } from '../utils.po.test';
|
||||
|
||||
const url = TestAppConfig.appUrl;
|
||||
const url = `${WebComponentConfig.appUrl}?OV_URL=${OPENVIDU_CALL_SERVER}`;
|
||||
|
||||
describe('Toolbar button functionality for local media control', () => {
|
||||
describe('Testing TOOLBAR features', () => {
|
||||
let browser: WebDriver;
|
||||
let utils: OpenViduComponentsPO;
|
||||
async function createChromeBrowser(): Promise<WebDriver> {
|
||||
return await new Builder()
|
||||
.forBrowser(TestAppConfig.browserName)
|
||||
.withCapabilities(TestAppConfig.browserCapabilities)
|
||||
.setChromeOptions(TestAppConfig.browserOptions)
|
||||
.usingServer(TestAppConfig.seleniumAddress)
|
||||
.forBrowser(WebComponentConfig.browserName)
|
||||
.withCapabilities(WebComponentConfig.browserCapabilities)
|
||||
.setChromeOptions(WebComponentConfig.browserOptions)
|
||||
.usingServer(WebComponentConfig.seleniumAddress)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
@ -22,13 +23,10 @@ describe('Toolbar button functionality for local media control', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
try {
|
||||
await utils.leaveRoom();
|
||||
} catch (error) {}
|
||||
await browser.quit();
|
||||
});
|
||||
|
||||
it('should toggle mute/unmute on the local microphone and update the icon accordingly', async () => {
|
||||
it('should mute and unmute the local microphone', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
||||
|
@ -45,7 +43,7 @@ describe('Toolbar button functionality for local media control', () => {
|
|||
expect(await utils.isPresent('#mic-btn #mic')).toBeTrue();
|
||||
});
|
||||
|
||||
it('should toggle mute/unmute on the local camera and update the icon accordingly', async () => {
|
||||
it('should mute and unmute the local camera', async () => {
|
||||
await browser.get(`${url}&prejoin=false`);
|
||||
|
||||
await utils.checkLayoutPresent();
|
|
@ -6,7 +6,7 @@ if [[ -z "$BASEHREF_VERSION" ]]; then
|
|||
fi
|
||||
|
||||
# Replace version from "stable" to the specified one in all TypeDoc links
|
||||
grep -rl '/latest/' projects src | xargs sed -i -e 's|/latest/|/'${BASEHREF_VERSION}'/|g'
|
||||
grep -rl '/en/stable/' projects src | xargs sed -i -e 's|/en/stable/|/en/'${BASEHREF_VERSION}'/|g'
|
||||
|
||||
# Replace testapp README by openvidu-components-angular README
|
||||
mv README.md README-testapp.md
|
||||
|
@ -16,7 +16,7 @@ cp ./projects/openvidu-components-angular/README.md .
|
|||
npm run doc:build
|
||||
|
||||
# Return links to "stable" version
|
||||
grep -rl '/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/'${BASEHREF_VERSION}'/|/latest/|g'
|
||||
grep -rl '/en/'${BASEHREF_VERSION}'/' projects src | xargs sed -i -e 's|/en/'${BASEHREF_VERSION}'/|/en/stable/|g'
|
||||
|
||||
# Undo changes with READMEs
|
||||
rm README.md
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
const fs = require('fs-extra');
|
||||
const concat = require('concat');
|
||||
const VERSION = require('./package.json').version;
|
||||
const ovWebcomponentRCPath = './dist/openvidu-webcomponent-rc';
|
||||
const ovWebcomponentProdPath = './dist/openvidu-webcomponent';
|
||||
|
||||
module.exports.buildWebcomponent = async () => {
|
||||
console.log('Building OpenVidu Web Component (' + VERSION + ')');
|
||||
const tutorialWcPath = '../../openvidu-tutorials/openvidu-webcomponent/web';
|
||||
const e2eWcPath = './e2e/webcomponent-app';
|
||||
|
||||
|
||||
try {
|
||||
await buildElement();
|
||||
await copyFiles(tutorialWcPath);
|
||||
await copyFiles(e2eWcPath);
|
||||
await renameWebComponentTestName(e2eWcPath);
|
||||
|
||||
console.log(`OpenVidu Web Component (${VERSION}) built`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
async function buildElement() {
|
||||
const files = [`${ovWebcomponentRCPath}/runtime.js`, `${ovWebcomponentRCPath}/main.js`, `${ovWebcomponentRCPath}/polyfills.js`];
|
||||
|
||||
try {
|
||||
await fs.ensureDir('./dist/openvidu-webcomponent');
|
||||
await concat(files, `${ovWebcomponentProdPath}/openvidu-webcomponent-${VERSION}.js`);
|
||||
await fs.copy(`${ovWebcomponentRCPath}/styles.css`, `${ovWebcomponentProdPath}/openvidu-webcomponent-${VERSION}.css`);
|
||||
// await fs.copy(
|
||||
// "./dist/openvidu-webcomponent/assets",
|
||||
// "./openvidu-webcomponent/assets"
|
||||
// );
|
||||
} catch (err) {
|
||||
console.error('Error executing build function in webcomponent-builds.js');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function renameWebComponentTestName(dir) {
|
||||
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.js`, `${dir}/openvidu-webcomponent-dev.js`);
|
||||
fs.renameSync(`${dir}/openvidu-webcomponent-${VERSION}.css`, `${dir}/openvidu-webcomponent-dev.css`);
|
||||
}
|
||||
|
||||
async function copyFiles(destination) {
|
||||
if (fs.existsSync(destination)) {
|
||||
try {
|
||||
console.log(`Copying openvidu-webcomponent files from: ${ovWebcomponentProdPath} to: ${destination}`);
|
||||
await fs.ensureDir(ovWebcomponentProdPath);
|
||||
await fs.copy(ovWebcomponentProdPath, destination);
|
||||
} catch (err) {
|
||||
console.error('Error executing copy function in webcomponent-builds.js');
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.buildWebcomponent();
|
File diff suppressed because it is too large
Load Diff
|
@ -1,33 +1,35 @@
|
|||
{
|
||||
"dependencies": {
|
||||
"@angular/animations": "19.2.8",
|
||||
"@angular/cdk": "19.2.11",
|
||||
"@angular/common": "19.2.8",
|
||||
"@angular/core": "19.2.8",
|
||||
"@angular/forms": "19.2.8",
|
||||
"@angular/material": "19.2.11",
|
||||
"@angular/platform-browser": "19.2.8",
|
||||
"@angular/platform-browser-dynamic": "19.2.8",
|
||||
"@angular/router": "19.2.8",
|
||||
"@livekit/track-processors": "^0.5.6",
|
||||
"@types/dom-mediacapture-transform": "^0.1.11",
|
||||
"@angular/animations": "18.2.5",
|
||||
"@angular/cdk": "18.2.5",
|
||||
"@angular/common": "18.2.5",
|
||||
"@angular/core": "18.2.5",
|
||||
"@angular/forms": "18.2.5",
|
||||
"@angular/material": "18.2.5",
|
||||
"@angular/platform-browser": "18.2.5",
|
||||
"@angular/platform-browser-dynamic": "18.2.5",
|
||||
"@angular/router": "18.2.5",
|
||||
"@livekit/track-processors": "0.3.2",
|
||||
"autolinker": "4.0.0",
|
||||
"livekit-client": "2.11.4",
|
||||
"livekit-client": "2.5.2",
|
||||
"rxjs": "7.8.1",
|
||||
"tslib": "2.7.0",
|
||||
"zone.js": "^0.15.0"
|
||||
"zone.js": "^0.14.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "19.2.9",
|
||||
"@angular/cli": "19.2.9",
|
||||
"@angular/compiler": "19.2.8",
|
||||
"@angular/compiler-cli": "19.2.8",
|
||||
"@angular-devkit/build-angular": "18.2.5",
|
||||
"@angular/cli": "18.2.5",
|
||||
"@angular/compiler": "18.2.5",
|
||||
"@angular/compiler-cli": "18.2.5",
|
||||
"@angular/elements": "18.2.5",
|
||||
"@compodoc/compodoc": "^1.1.25",
|
||||
"@types/dom-mediacapture-transform": "0.1.9",
|
||||
"@types/dom-webcodecs": "0.1.11",
|
||||
"@types/jasmine": "^5.1.4",
|
||||
"@types/node": "20.12.14",
|
||||
"@types/selenium-webdriver": "4.1.16",
|
||||
"@types/ws": "^8.5.12",
|
||||
"chromedriver": "138.0.0",
|
||||
"chromedriver": "129.0.0",
|
||||
"concat": "^1.0.3",
|
||||
"cross-env": "^7.0.3",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
|
@ -47,13 +49,13 @@
|
|||
"karma-mocha-reporter": "2.2.5",
|
||||
"karma-notify-reporter": "1.3.0",
|
||||
"lint-staged": "^15.2.10",
|
||||
"ng-packagr": "19.2.2",
|
||||
"ng-packagr": "18.2.1",
|
||||
"npm-watch": "^0.13.0",
|
||||
"prettier": "3.3.3",
|
||||
"selenium-webdriver": "4.32.0",
|
||||
"selenium-webdriver": "4.25.0",
|
||||
"ts-node": "10.9.2",
|
||||
"tslint": "6.1.3",
|
||||
"typescript": "5.8.3",
|
||||
"typescript": "5.4.5",
|
||||
"webpack-bundle-analyzer": "^4.10.2"
|
||||
},
|
||||
"name": "openvidu-components-testapp",
|
||||
|
@ -73,6 +75,7 @@
|
|||
"start-prod": "npx http-server ./dist/openvidu-components-testapp/browser --port 4200",
|
||||
"start:ssl": "ng serve --ssl --configuration development --host 0.0.0.0 --port 5080",
|
||||
"build": "ng build openvidu-components-testapp --configuration production",
|
||||
"bundle-report": "ng build openvidu-webcomponent --stats-json --configuration production && webpack-bundle-analyzer dist/openvidu-webcomponent/stats.json",
|
||||
"doc:build": "npx compodoc -c ./projects/openvidu-components-angular/doc/.compodocrc.json",
|
||||
"doc:generate-directives-tutorials": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tutorials.js",
|
||||
"doc:generate-directive-tables": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tables.js",
|
||||
|
@ -82,23 +85,26 @@
|
|||
"lib:serve": "ng build openvidu-components-angular --watch",
|
||||
"lib:build": "ng build openvidu-components-angular --configuration production && cd ./dist/openvidu-components-angular",
|
||||
"lib:pack": "cd ./dist/openvidu-components-angular && npm pack",
|
||||
"lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call/frontend",
|
||||
"lib:copy": "cp dist/openvidu-components-angular/openvidu-components-angular-*.tgz ../../openvidu-call/openvidu-call-front",
|
||||
"lib:test": "ng test openvidu-components-angular --no-watch --code-coverage",
|
||||
"e2e:nested-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/*.test.js",
|
||||
"e2e:nested-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/events.test.js",
|
||||
"e2e:nested-structural-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/structural-directives.test.js",
|
||||
"e2e:nested-attribute-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/attribute-directives.test.js",
|
||||
"e2e:lib-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/api-directives.test.js",
|
||||
"e2e:lib-internal-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/internal-directives.test.js",
|
||||
"e2e:lib-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/chat.test.js",
|
||||
"e2e:lib-events": "tsc --project ./e2e && npx jasmine ./e2e/dist/events.test.js",
|
||||
"e2e:lib-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/media-devices.test.js",
|
||||
"e2e:lib-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/panels.test.js",
|
||||
"e2e:lib-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/screensharing.test.js",
|
||||
"e2e:lib-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/stream.test.js",
|
||||
"e2e:lib-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/toolbar.test.js",
|
||||
"e2e:nested-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/directives.test.js",
|
||||
"e2e:webcomponent-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/**/*.test.js",
|
||||
"e2e:webcomponent-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/api-directives.test.js",
|
||||
"e2e:webcomponent-captions": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/captions.test.js",
|
||||
"e2e:webcomponent-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/chat.test.js",
|
||||
"e2e:webcomponent-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/events.test.js",
|
||||
"e2e:webcomponent-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/media-devices.test.js",
|
||||
"e2e:webcomponent-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/panels.test.js",
|
||||
"e2e:webcomponent-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/screensharing.test.js",
|
||||
"e2e:webcomponent-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/stream.test.js",
|
||||
"e2e:webcomponent-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/toolbar.test.js",
|
||||
"webcomponent:testing-build": "./node_modules/@angular/cli/bin/ng.js build openvidu-webcomponent --configuration testing && node ./openvidu-webcomponent-build.js",
|
||||
"webcomponent:build": "./node_modules/@angular/cli/bin/ng.js build openvidu-webcomponent --configuration production && node ./openvidu-webcomponent-build.js",
|
||||
"webcomponent:serve-testapp": "npx http-server ./e2e/webcomponent-app/",
|
||||
"simulate:multiparty": "livekit-cli load-test --url ws://localhost:7880 --api-key devkey --api-secret secret --room daily-call --publishers 8 --audio-publishers 8 --identity-prefix Participant --identity publisher",
|
||||
"husky": "cd .. && husky install"
|
||||
},
|
||||
"version": "3.3.0"
|
||||
"version": "3.0.0"
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ Angular Components are the simplest way to create real-time videoconferencing ap
|
|||
|
||||
## Getting Started
|
||||
|
||||
To get started with OpenVidu Components Angular, visit our [**Getting Started guide**](https://openvidu.io/latest/docs/ui-components/angular-components/).
|
||||
To get started with OpenVidu Components Angular, visit our [**Getting Started guide**](https://openvidu.io/docs/ui-components/angular-components/).
|
||||
|
||||
1. Create an Angular Project (>= 17.0.0)
|
||||
|
||||
|
@ -92,7 +92,7 @@ You can also customize the styles in your `styles.scss` file:
|
|||
|
||||
## API Documentation
|
||||
|
||||
For detailed information on OpenVidu Angular Components, refer to our [**API Reference**](https://openvidu.io/latest/docs/reference-docs/openvidu-components-angular/).
|
||||
For detailed information on OpenVidu Angular Components, refer to our [**API Reference**](https://openvidu.io/docs/reference-docs/openvidu-components-angular).
|
||||
|
||||
## Support
|
||||
|
||||
|
|
|
@ -7,33 +7,24 @@ const apiDirectivesTable =
|
|||
'|:--------------------------------: | :-------: | :---------------------------------------------: |';
|
||||
const endApiLine = '<!-- end-dynamic-api-directives-content -->';
|
||||
|
||||
/**
|
||||
* Get all directive files from the API directives directory
|
||||
*/
|
||||
function getDirectiveFiles() {
|
||||
// Directory where directive files are located
|
||||
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
|
||||
return listFiles(directivesDir, '.directive.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all component files
|
||||
*/
|
||||
function getComponentFiles() {
|
||||
// Directory where component files are located
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all admin files
|
||||
*/
|
||||
function getAdminFiles() {
|
||||
// Directory where component files are located
|
||||
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin';
|
||||
return listFiles(componentsDir, '.component.ts');
|
||||
}
|
||||
|
||||
/**
|
||||
* List all files with specific extension in directory
|
||||
*/
|
||||
function listFiles(directoryPath, fileExtension) {
|
||||
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
|
||||
if (files.length === 0) {
|
||||
|
@ -42,265 +33,128 @@ function listFiles(directoryPath, fileExtension) {
|
|||
return files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract component selector from component file
|
||||
*/
|
||||
function getComponentSelector(componentFile) {
|
||||
const componentContent = fs.readFileSync(componentFile, 'utf8');
|
||||
const selectorMatch = componentContent.match(/@Component\({[^]*?selector:\s*['"]([^'"]+)['"][^]*?}\)/s);
|
||||
|
||||
if (!selectorMatch) {
|
||||
throw new Error(`Unable to find selector in component file: ${componentFile}`);
|
||||
}
|
||||
|
||||
return selectorMatch[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a directive class has @internal annotation
|
||||
*/
|
||||
function isInternalDirective(directiveContent, className) {
|
||||
const classRegex = new RegExp(`(/\\*\\*[\\s\\S]*?\\*/)?\\s*@Directive\\([\\s\\S]*?\\)\\s*export\\s+class\\s+${escapeRegex(className)}`, 'g');
|
||||
const match = classRegex.exec(directiveContent);
|
||||
|
||||
if (match && match[1]) {
|
||||
return match[1].includes('@internal');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract attribute name from selector for a specific component
|
||||
*/
|
||||
function extractAttributeForComponent(selector, componentSelector) {
|
||||
// Split selector by comma and trim whitespace
|
||||
const selectorParts = selector.split(',').map(part => part.trim());
|
||||
|
||||
// Find the part that matches our component
|
||||
for (const part of selectorParts) {
|
||||
if (part.includes(componentSelector)) {
|
||||
// Extract attribute from this specific part
|
||||
const attributeMatch = part.match(/\[([^\]]+)\]/);
|
||||
if (attributeMatch) {
|
||||
return attributeMatch[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: if no specific match, return the first attribute found
|
||||
const fallbackMatch = selector.match(/\[([^\]]+)\]/);
|
||||
return fallbackMatch ? fallbackMatch[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all directive classes from a directive file
|
||||
*/
|
||||
function extractDirectiveClasses(directiveContent) {
|
||||
const classes = [];
|
||||
|
||||
// Regex to find all directive class definitions with their preceding @Directive decorators
|
||||
const directiveClassRegex = /@Directive\(\s*{\s*selector:\s*['"]([^'"]+)['"][^}]*}\s*\)\s*export\s+class\s+(\w+)/gs;
|
||||
|
||||
let match;
|
||||
while ((match = directiveClassRegex.exec(directiveContent)) !== null) {
|
||||
const selector = match[1];
|
||||
const className = match[2];
|
||||
|
||||
// Skip internal directives
|
||||
if (isInternalDirective(directiveContent, className)) {
|
||||
console.log(`Skipping internal directive: ${className}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
classes.push({
|
||||
selector,
|
||||
className
|
||||
});
|
||||
}
|
||||
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract all directives from a directive file that match a component selector
|
||||
*/
|
||||
function extractDirectivesForComponent(directiveFile, componentSelector) {
|
||||
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
|
||||
const directives = [];
|
||||
|
||||
// Get all directive classes in the file (excluding internal ones)
|
||||
const directiveClasses = extractDirectiveClasses(directiveContent);
|
||||
|
||||
// Filter classes that match the component selector
|
||||
const matchingClasses = directiveClasses.filter(directiveClass =>
|
||||
directiveClass.selector.includes(componentSelector)
|
||||
);
|
||||
|
||||
// For each matching class, extract input type information
|
||||
matchingClasses.forEach(directiveClass => {
|
||||
// Extract the correct attribute name for this component
|
||||
const attributeName = extractAttributeForComponent(directiveClass.selector, componentSelector);
|
||||
|
||||
if (attributeName) {
|
||||
const inputInfo = extractInputInfo(directiveContent, attributeName, directiveClass.className);
|
||||
|
||||
if (inputInfo) {
|
||||
directives.push({
|
||||
attribute: attributeName,
|
||||
type: inputInfo.type,
|
||||
className: directiveClass.className
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return directives;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract input information (type) for a specific attribute and class
|
||||
*/
|
||||
function extractInputInfo(directiveContent, attributeName, className) {
|
||||
// Create a regex to find the specific class section
|
||||
const classRegex = new RegExp(`export\\s+class\\s+${escapeRegex(className)}[^}]*?{([^]*?)(?=export\\s+class|$)`, 's');
|
||||
const classMatch = directiveContent.match(classRegex);
|
||||
|
||||
if (!classMatch) {
|
||||
console.warn(`Could not find class ${className}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const classContent = classMatch[1];
|
||||
|
||||
// Regex to find the @Input setter for this attribute within the class
|
||||
const inputRegex = new RegExp(
|
||||
`@Input\\(\\)\\s+set\\s+${escapeRegex(attributeName)}\\s*\\(\\s*\\w+:\\s*([^)]+)\\s*\\)`,
|
||||
'g'
|
||||
);
|
||||
|
||||
const inputMatch = inputRegex.exec(classContent);
|
||||
if (!inputMatch) {
|
||||
console.warn(`Could not find @Input setter for attribute: ${attributeName} in class: ${className}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
let type = inputMatch[1].trim();
|
||||
|
||||
// Clean up the type (remove extra whitespace, etc.)
|
||||
type = type.replace(/\s+/g, ' ');
|
||||
|
||||
return {
|
||||
type: type
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Escape special regex characters
|
||||
*/
|
||||
function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate API directives table for components
|
||||
*/
|
||||
function generateApiDirectivesTable(componentFiles, directiveFiles) {
|
||||
componentFiles.forEach((componentFile) => {
|
||||
try {
|
||||
console.log(`Processing component: ${componentFile}`);
|
||||
|
||||
const componentSelector = getComponentSelector(componentFile);
|
||||
const readmeFilePath = componentFile.replace('.ts', '.md');
|
||||
|
||||
console.log(`Component selector: ${componentSelector}`);
|
||||
|
||||
// Initialize table with header
|
||||
initializeDynamicTableContent(readmeFilePath);
|
||||
|
||||
const allDirectives = [];
|
||||
|
||||
// Extract directives from all directive files
|
||||
directiveFiles.forEach((directiveFile) => {
|
||||
console.log(`Checking directive file: ${directiveFile}`);
|
||||
const directives = extractDirectivesForComponent(directiveFile, componentSelector);
|
||||
allDirectives.push(...directives);
|
||||
});
|
||||
|
||||
console.log(`Found ${allDirectives.length} directives for ${componentSelector}`);
|
||||
|
||||
// Sort directives alphabetically by attribute name
|
||||
allDirectives.sort((a, b) => a.attribute.localeCompare(b.attribute));
|
||||
|
||||
// Add rows to table
|
||||
allDirectives.forEach((directive) => {
|
||||
addRowToTable(readmeFilePath, directive.attribute, directive.type, directive.className);
|
||||
});
|
||||
|
||||
// If no directives found, add "no directives" message
|
||||
if (allDirectives.length === 0) {
|
||||
removeApiTableContent(readmeFilePath);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`Error processing component ${componentFile}:`, error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize table with header
|
||||
*/
|
||||
function initializeDynamicTableContent(filePath) {
|
||||
replaceDynamicTableContent(filePath, apiDirectivesTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace table content with "no directives" message
|
||||
*/
|
||||
function removeApiTableContent(filePath) {
|
||||
const content = '_No API directives available for this component_. \n';
|
||||
replaceDynamicTableContent(filePath, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a row to the markdown table
|
||||
*/
|
||||
function addRowToTable(filePath, parameter, type, reference) {
|
||||
function apiTableContentIsEmpty(filePath) {
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const startIdx = data.indexOf(startApiLine);
|
||||
const endIdx = data.indexOf(endApiLine);
|
||||
if (startIdx !== -1 && endIdx !== -1) {
|
||||
const capturedContent = data.substring(startIdx + startApiLine.length, endIdx).trim();
|
||||
return capturedContent === apiDirectivesTable;
|
||||
}
|
||||
return false;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function writeApiDirectivesTable(componentFiles, directiveFiles) {
|
||||
componentFiles.forEach((componentFile) => {
|
||||
// const componentName = componentFile.split('/').pop()
|
||||
const componentFileName = componentFile.split('/').pop().replace('.component.ts', '');
|
||||
const componentName = componentFileName.replace(/(?:^|-)([a-z])/g, (_, char) => char.toUpperCase());
|
||||
const readmeFilePath = componentFile.replace('.ts', '.md');
|
||||
const componentContent = fs.readFileSync(componentFile, 'utf8');
|
||||
const selectorMatch = componentContent.match(/@Component\({[^]*?selector: ['"]([^'"]+)['"][^]*?}\)/);
|
||||
const componentSelectorName = selectorMatch[1];
|
||||
initializeDynamicTableContent(readmeFilePath);
|
||||
|
||||
if (!componentSelectorName) {
|
||||
throw new Error(`Unable to find the component name in the file ${componentFileName}`);
|
||||
}
|
||||
|
||||
// const directiveRegex = new RegExp(`@Directive\\(\\s*{[^}]*selector:\\s*['"]${componentName}\\s*\\[([^'"]+)\\]`, 'g');
|
||||
const directiveRegex = /^\s*(selector):\s*(['"])(.*?)\2\s*$/gm;
|
||||
|
||||
directiveFiles.forEach((directiveFile) => {
|
||||
const directiveContent = fs.readFileSync(directiveFile, 'utf8');
|
||||
|
||||
let directiveNameMatch;
|
||||
while ((directiveNameMatch = directiveRegex.exec(directiveContent)) !== null) {
|
||||
if (directiveNameMatch[0].includes('@Directive({\n//')) {
|
||||
// Skip directives that are commented out
|
||||
continue;
|
||||
}
|
||||
const selectorValue = directiveNameMatch[3].split(',');
|
||||
const directiveMatch = selectorValue.find((value) => value.includes(componentSelectorName));
|
||||
|
||||
if (directiveMatch) {
|
||||
const directiveName = directiveMatch.match(/\[(.*?)\]/).pop();
|
||||
const className = directiveName.replace(/(^\w{1})|(\s+\w{1})/g, (letter) => letter.toUpperCase()) + 'Directive';
|
||||
const inputRegex = new RegExp(
|
||||
`@Input\\(\\)\\s+set\\s+(${directiveName.replace(/\[/g, '\\[').replace(/\]/g, '\\]')})\\((\\w+):\\s+(\\w+)`
|
||||
);
|
||||
const inputMatch = directiveContent.match(inputRegex);
|
||||
const inputType = inputMatch && inputMatch.pop();
|
||||
|
||||
if (inputType && className) {
|
||||
let finalClassName = componentName === 'Videoconference' ? className : componentName + className;
|
||||
addRowToTable(readmeFilePath, directiveName, inputType, finalClassName);
|
||||
}
|
||||
} else {
|
||||
console.log(`The selector "${componentSelectorName}" does not match with ${selectorValue}. Skipping...`);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (apiTableContentIsEmpty(readmeFilePath)) {
|
||||
removeApiTableContent(readmeFilePath);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Function to add a row to a Markdown table in a file
|
||||
function addRowToTable(filePath, parameter, type, reference) {
|
||||
// Read the current content of the file
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Define the target line and the Markdown row
|
||||
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
|
||||
|
||||
// Find the line that contains the table
|
||||
const lines = data.split('\n');
|
||||
const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
|
||||
|
||||
if (targetIndex !== -1) {
|
||||
// Insert the new row above the target line
|
||||
lines.splice(targetIndex, 0, markdownRow);
|
||||
|
||||
// Join the lines back together
|
||||
const updatedContent = lines.join('\n');
|
||||
|
||||
// Write the updated content to the file
|
||||
fs.writeFileSync(filePath, updatedContent, 'utf8');
|
||||
console.log(`Added directive: ${parameter} -> ${reference}`);
|
||||
console.log('Row added successfully.');
|
||||
} else {
|
||||
console.error('End marker not found in file:', filePath);
|
||||
console.error('Table not found in the file.');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error adding row to table:', error);
|
||||
console.error('Error writing to file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace content between start and end markers
|
||||
*/
|
||||
function replaceDynamicTableContent(filePath, content) {
|
||||
// Read the current content of the file
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
|
||||
|
||||
// Replace the content between startLine and endLine with the replacement table
|
||||
const modifiedContent = data.replace(pattern, (match, capturedContent) => {
|
||||
return startApiLine + '\n' + content + '\n' + endApiLine;
|
||||
});
|
||||
|
||||
// Write the modified content back to the file
|
||||
fs.writeFileSync(filePath, modifiedContent, 'utf8');
|
||||
console.log(`Updated table content in: ${filePath}`);
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
|
||||
|
@ -310,27 +164,7 @@ function replaceDynamicTableContent(filePath, content) {
|
|||
}
|
||||
}
|
||||
|
||||
// Main execution
|
||||
if (require.main === module) {
|
||||
try {
|
||||
const directiveFiles = getDirectiveFiles();
|
||||
const componentFiles = getComponentFiles();
|
||||
const adminFiles = getAdminFiles();
|
||||
|
||||
console.log('Starting directive table generation...');
|
||||
generateApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
|
||||
console.log('Directive table generation completed!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Script execution failed:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Export functions for testing
|
||||
module.exports = {
|
||||
generateApiDirectivesTable,
|
||||
getDirectiveFiles,
|
||||
getComponentFiles,
|
||||
getAdminFiles
|
||||
};
|
||||
const directiveFiles = getDirectiveFiles();
|
||||
const componentFiles = getComponentFiles();
|
||||
const adminFiles = getAdminFiles();
|
||||
writeApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
|
||||
|
|
|
@ -5,7 +5,8 @@
|
|||
"../src/lib/directives/**/*.ts",
|
||||
"../src/lib/services/**/*.ts",
|
||||
"../src/lib/models/**/*.ts",
|
||||
"../src/lib/pipes/**/*.ts"
|
||||
"../src/lib/pipes/**/*.ts",
|
||||
// "../../../src/app/openvidu-webcomponent/**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"src/test.ts",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "openvidu-components-angular",
|
||||
"version": "3.3.0",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "openvidu-components-angular",
|
||||
"version": "3.3.0",
|
||||
"version": "3.0.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
|
|
|
@ -4,16 +4,16 @@
|
|||
},
|
||||
"name": "openvidu-components-angular",
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/cdk": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/material": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"@angular/animations": "^17.0.0 || ^18.0.0",
|
||||
"@angular/cdk": "^17.0.0 || ^18.0.0",
|
||||
"@angular/common": "^17.0.0 || ^18.0.0",
|
||||
"@angular/core": "^17.0.0 || ^18.0.0",
|
||||
"@angular/forms": "^17.0.0 || ^18.0.0",
|
||||
"@angular/material": "^17.0.0 || ^18.0.0",
|
||||
"autolinker": "^4.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"livekit-client": "^2.1.0",
|
||||
"@livekit/track-processors": "^0.3.2"
|
||||
},
|
||||
"version": "3.3.0"
|
||||
"version": "3.0.0"
|
||||
}
|
||||
|
|
|
@ -4,6 +4,5 @@ With the following directives you can modify the default User Interface with the
|
|||
<!-- start-dynamic-api-directives-content -->
|
||||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **navbarTitle** | `string` | [AdminDashboardTitleDirective](../directives/AdminDashboardTitleDirective.html) |
|
||||
| **recordingsList** | `RecordingInfo[]` | [AdminDashboardRecordingsListDirective](../directives/AdminDashboardRecordingsListDirective.html) |
|
||||
| **recordingsList** | `RecordingInfo` | [AdminDashboardRecordingsListDirective](../directives/AdminDashboardRecordingsListDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
|
@ -8,8 +8,7 @@ import { RecordingService } from '../../services/recording/recording.service';
|
|||
@Component({
|
||||
selector: 'ov-admin-dashboard',
|
||||
templateUrl: './admin-dashboard.component.html',
|
||||
styleUrls: ['./admin-dashboard.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./admin-dashboard.component.scss']
|
||||
})
|
||||
export class AdminDashboardComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
|
|
|
@ -6,5 +6,4 @@ With the following directives you can modify the default User Interface with the
|
|||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **error** | `any` | [AdminLoginErrorDirective](../directives/AdminLoginErrorDirective.html) |
|
||||
| **navbarTitle** | `any` | [AdminLoginTitleDirective](../directives/AdminLoginTitleDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
|
@ -7,8 +7,7 @@ import { OpenViduComponentsConfigService } from '../../services/config/directive
|
|||
@Component({
|
||||
selector: 'ov-admin-login',
|
||||
templateUrl: './admin-login.component.html',
|
||||
styleUrls: ['./admin-login.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./admin-login.component.scss']
|
||||
})
|
||||
export class AdminLoginComponent implements OnInit {
|
||||
/**
|
||||
|
|
|
@ -10,7 +10,6 @@ import { Component } from '@angular/core';
|
|||
<div class="stick loud play"></div>
|
||||
<div class="stick normal play"></div>
|
||||
</div>`,
|
||||
styleUrls: ['./audio-wave.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./audio-wave.component.scss']
|
||||
})
|
||||
export class AudioWaveComponent {}
|
||||
|
|
|
@ -13,8 +13,7 @@ import { Component, Input } from '@angular/core';
|
|||
</div>
|
||||
</div>
|
||||
`,
|
||||
styleUrls: ['./avatar-profile.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./avatar-profile.component.scss']
|
||||
})
|
||||
export class AvatarProfileComponent {
|
||||
letter: string;
|
||||
|
|
|
@ -36,8 +36,7 @@ import { MatDialogRef } from '@angular/material/dialog';
|
|||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
`
|
||||
],
|
||||
standalone: false
|
||||
]
|
||||
})
|
||||
export class DeleteDialogComponent {
|
||||
constructor(public dialogRef: MatDialogRef<DeleteDialogComponent>) {}
|
||||
|
|
|
@ -33,8 +33,7 @@ import { DialogData } from '../../models/dialog.model';
|
|||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
`
|
||||
],
|
||||
standalone: false
|
||||
]
|
||||
})
|
||||
export class DialogTemplateComponent {
|
||||
constructor(
|
||||
|
|
|
@ -19,8 +19,7 @@ import { DialogData } from '../../models/dialog.model';
|
|||
</button>
|
||||
<button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button>
|
||||
</div>
|
||||
`,
|
||||
standalone: false
|
||||
`
|
||||
})
|
||||
export class ProFeatureDialogTemplateComponent {
|
||||
constructor(public dialogRef: MatDialogRef<ProFeatureDialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
|
||||
|
@ -30,6 +29,6 @@ export class ProFeatureDialogTemplateComponent {
|
|||
}
|
||||
|
||||
seeMore() {
|
||||
window.open('https://openvidu.io/pricing/#openvidu-pro', '_blank')?.focus();
|
||||
window.open('https://docs.openvidu.io/en/stable/openvidu-pro/', '_blank')?.focus();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,8 +38,7 @@ import { RecordingDialogData } from '../../models/dialog.model';
|
|||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
`
|
||||
],
|
||||
standalone: false
|
||||
]
|
||||
})
|
||||
export class RecordingDialogComponent {
|
||||
@ViewChild('videoElement', { static: true }) videoElement: ElementRef<HTMLVideoElement>;
|
||||
|
|
|
@ -19,11 +19,6 @@
|
|||
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
|
||||
</div>
|
||||
|
||||
<!-- Render additional layout elements injected via ovAdditionalLayoutElement -->
|
||||
@if (layoutAdditionalElementsTemplate) {
|
||||
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
|
||||
}
|
||||
|
||||
<div
|
||||
*ngFor="let track of remoteParticipants | tracks; trackBy: trackParticipantElement"
|
||||
class="remote-participant"
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { LayoutAdditionalElementsDirective } from '../../directives/template/internals.directive';
|
||||
|
||||
import {
|
||||
AfterViewInit,
|
||||
ChangeDetectionStrategy,
|
||||
|
@ -13,7 +11,7 @@ import {
|
|||
ViewChild,
|
||||
ViewContainerRef
|
||||
} from '@angular/core';
|
||||
import { combineLatest, map, Subject, takeUntil } from 'rxjs';
|
||||
import { combineLatest, map, Subscription } from 'rxjs';
|
||||
import { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
|
||||
import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model';
|
||||
import { LayoutService } from '../../services/layout/layout.service';
|
||||
|
@ -21,8 +19,8 @@ import { ParticipantService } from '../../services/participant/participant.servi
|
|||
import { CdkDrag } from '@angular/cdk/drag-drop';
|
||||
import { PanelService } from '../../services/panel/panel.service';
|
||||
import { GlobalConfigService } from '../../services/config/global-config.service';
|
||||
import { ServiceConfigService } from '../../services/config/service-config.service';
|
||||
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
||||
import { LayoutTemplateConfiguration, TemplateManagerService } from '../../services/template/template-manager.service';
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -33,8 +31,7 @@ import { LayoutTemplateConfiguration, TemplateManagerService } from '../../servi
|
|||
selector: 'ov-layout',
|
||||
templateUrl: './layout.component.html',
|
||||
styleUrls: ['./layout.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
/**
|
||||
|
@ -42,11 +39,6 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
@ContentChild('stream', { read: TemplateRef }) streamTemplate: TemplateRef<any>;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild('layoutAdditionalElements', { read: TemplateRef }) layoutAdditionalElementsTemplate: TemplateRef<any>;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -70,27 +62,9 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
// is inside of the layout component tagged with '*ovLayout' directive
|
||||
if (externalStream) {
|
||||
this.streamTemplate = externalStream.template;
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild(LayoutAdditionalElementsDirective) set externalAdditionalElements(
|
||||
externalAdditionalElements: LayoutAdditionalElementsDirective
|
||||
) {
|
||||
if (externalAdditionalElements) {
|
||||
this._externalLayoutAdditionalElements = externalAdditionalElements;
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
templateConfig: LayoutTemplateConfiguration = {};
|
||||
|
||||
localParticipant: ParticipantModel | undefined;
|
||||
remoteParticipants: ParticipantModel[] = [];
|
||||
/**
|
||||
|
@ -98,31 +72,31 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
captionsEnabled = true;
|
||||
|
||||
private _externalStream?: StreamDirective;
|
||||
private _externalLayoutAdditionalElements?: LayoutAdditionalElementsDirective;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private localParticipantSubs: Subscription;
|
||||
private remoteParticipantsSubs: Subscription;
|
||||
private captionsSubs: Subscription;
|
||||
private resizeObserver: ResizeObserver;
|
||||
private cdkSubscription: Subscription;
|
||||
private resizeTimeout: NodeJS.Timeout;
|
||||
private videoIsAtRight: boolean = false;
|
||||
private lastLayoutWidth: number = 0;
|
||||
private layoutService: LayoutService;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private serviceConfig: ServiceConfigService,
|
||||
private panelService: PanelService,
|
||||
private participantService: ParticipantService,
|
||||
private globalService: GlobalConfigService,
|
||||
private directiveService: OpenViduComponentsConfigService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private templateManagerService: TemplateManagerService
|
||||
) {}
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
this.layoutService = this.serviceConfig.getLayoutService();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.setupTemplates();
|
||||
|
||||
this.subscribeToParticipants();
|
||||
this.subscribeToCaptions();
|
||||
}
|
||||
|
@ -136,11 +110,13 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.localParticipant = undefined;
|
||||
this.remoteParticipants = [];
|
||||
this.resizeObserver?.disconnect();
|
||||
this.localParticipantSubs?.unsubscribe();
|
||||
this.remoteParticipantsSubs?.unsubscribe();
|
||||
this.captionsSubs?.unsubscribe();
|
||||
this.cdkSubscription?.unsubscribe();
|
||||
this.layoutService.clear();
|
||||
}
|
||||
|
||||
|
@ -153,36 +129,8 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
return track;
|
||||
}
|
||||
|
||||
private setupTemplates() {
|
||||
this.templateConfig = this.templateManagerService.setupLayoutTemplates(
|
||||
this._externalStream,
|
||||
this._externalLayoutAdditionalElements
|
||||
);
|
||||
|
||||
// Apply templates to component properties for backward compatibility
|
||||
this.applyTemplateConfiguration();
|
||||
}
|
||||
|
||||
private applyTemplateConfiguration() {
|
||||
if (this.templateConfig.layoutStreamTemplate) {
|
||||
this.streamTemplate = this.templateConfig.layoutStreamTemplate;
|
||||
}
|
||||
if (this.templateConfig.layoutAdditionalElementsTemplate) {
|
||||
this.layoutAdditionalElementsTemplate = this.templateConfig.layoutAdditionalElementsTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Updates templates and triggers change detection
|
||||
*/
|
||||
private updateTemplatesAndMarkForCheck(): void {
|
||||
this.setupTemplates();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
private subscribeToCaptions() {
|
||||
this.layoutService.captionsTogglingObs.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
|
||||
this.captionsEnabled = value;
|
||||
this.cd.markForCheck();
|
||||
this.layoutService.update();
|
||||
|
@ -190,7 +138,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToParticipants() {
|
||||
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe((p) => {
|
||||
this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p) => {
|
||||
if (p) {
|
||||
this.localParticipant = p;
|
||||
if (!this.localParticipant?.isMinimized) {
|
||||
|
@ -201,12 +149,14 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
});
|
||||
|
||||
combineLatest([this.participantService.remoteParticipants$, this.directiveService.layoutRemoteParticipants$])
|
||||
this.remoteParticipantsSubs = combineLatest([
|
||||
this.participantService.remoteParticipants$,
|
||||
this.directiveService.layoutRemoteParticipants$
|
||||
])
|
||||
.pipe(
|
||||
map(([serviceParticipants, directiveParticipants]) =>
|
||||
directiveParticipants !== undefined ? directiveParticipants : serviceParticipants
|
||||
),
|
||||
takeUntil(this.destroy$)
|
||||
)
|
||||
)
|
||||
.subscribe((participants) => {
|
||||
this.remoteParticipants = participants;
|
||||
|
@ -271,8 +221,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.videoIsAtRight = false;
|
||||
}
|
||||
};
|
||||
|
||||
this.cdkDrag.released.pipe(takeUntil(this.destroy$)).subscribe(handler);
|
||||
this.cdkSubscription = this.cdkDrag.released.subscribe(handler);
|
||||
|
||||
if (this.globalService.isProduction()) return;
|
||||
// Just for allow E2E testing with drag and drop
|
||||
|
|
|
@ -18,8 +18,7 @@ import { Track } from 'livekit-client';
|
|||
transition(':enter', [style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]),
|
||||
transition(':leave', [style({ opacity: 1 }), animate('200ms', style({ opacity: 0 }))])
|
||||
])
|
||||
],
|
||||
standalone: false
|
||||
]
|
||||
})
|
||||
export class MediaElementComponent implements AfterViewInit {
|
||||
_track: Track;
|
||||
|
|
|
@ -17,8 +17,6 @@
|
|||
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
|
||||
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
|
||||
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
|
||||
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
|
||||
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
|
||||
></ov-recording-activity>
|
||||
<ov-broadcasting-activity
|
||||
*ngIf="showBroadcastingActivity"
|
||||
|
|
|
@ -4,6 +4,6 @@ With the following directives you can modify the default User Interface with the
|
|||
<!-- start-dynamic-api-directives-content -->
|
||||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
|
||||
| **recordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) |
|
||||
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { PanelStatusInfo, PanelType } from '../../../models/panel.model';
|
||||
import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service';
|
||||
import { PanelService } from '../../../services/panel/panel.service';
|
||||
|
@ -20,8 +20,7 @@ import { BroadcastingStartRequestedEvent, BroadcastingStopRequestedEvent } from
|
|||
selector: 'ov-activities-panel',
|
||||
templateUrl: './activities-panel.component.html',
|
||||
styleUrls: ['../panel.component.scss', './activities-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ActivitiesPanelComponent implements OnInit {
|
||||
/**
|
||||
|
@ -54,21 +53,6 @@ export class ActivitiesPanelComponent implements OnInit {
|
|||
*/
|
||||
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Provides event notifications that fire when view recordings button has been clicked.
|
||||
* This event is triggered when the user wants to view all recordings in an external page.
|
||||
*/
|
||||
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Provides event notifications that fire when view recording button has been clicked.
|
||||
* This event is triggered when the user wants to view a specific recording in an external page.
|
||||
* It provides the recording ID as event data.
|
||||
*/
|
||||
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
/**
|
||||
* Provides event notifications that fire when start broadcasting button is clicked.
|
||||
* It provides the {@link BroadcastingStartRequestedEvent} payload as event data.
|
||||
|
@ -95,7 +79,9 @@ export class ActivitiesPanelComponent implements OnInit {
|
|||
* @internal
|
||||
*/
|
||||
showBroadcastingActivity: boolean = true;
|
||||
private destroy$ = new Subject<void>();
|
||||
private panelSubscription: Subscription;
|
||||
private recordingActivitySub: Subscription;
|
||||
private broadcastingActivitySub: Subscription;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -118,8 +104,9 @@ export class ActivitiesPanelComponent implements OnInit {
|
|||
* @internal
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.panelSubscription) this.panelSubscription.unsubscribe();
|
||||
if (this.recordingActivitySub) this.recordingActivitySub.unsubscribe();
|
||||
if (this.broadcastingActivitySub) this.broadcastingActivitySub.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -130,7 +117,7 @@ export class ActivitiesPanelComponent implements OnInit {
|
|||
}
|
||||
|
||||
private subscribeToPanelToggling() {
|
||||
this.panelService.panelStatusObs.pipe(takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.panelSubscription = this.panelService.panelStatusObs.subscribe((ev: PanelStatusInfo) => {
|
||||
if (ev.panelType === PanelType.ACTIVITIES && !!ev.subOptionType) {
|
||||
this.expandedPanel = ev.subOptionType;
|
||||
}
|
||||
|
@ -138,12 +125,12 @@ export class ActivitiesPanelComponent implements OnInit {
|
|||
}
|
||||
|
||||
private subscribeToActivitiesPanelDirective() {
|
||||
this.libService.recordingActivity$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.recordingActivitySub = this.libService.recordingActivity$.subscribe((value: boolean) => {
|
||||
this.showRecordingActivity = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.broadcastingActivity$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.broadcastingActivitySub = this.libService.broadcastingActivity$.subscribe((value: boolean) => {
|
||||
this.showBroadcastingActivity = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
BroadcastingStartRequestedEvent,
|
||||
BroadcastingStatus,
|
||||
|
@ -18,8 +18,7 @@ import { OpenViduService } from '../../../../services/openvidu/openvidu.service'
|
|||
selector: 'ov-broadcasting-activity',
|
||||
templateUrl: './broadcasting-activity.component.html',
|
||||
styleUrls: ['./broadcasting-activity.component.scss', '../activities-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
|
||||
// TODO: Allow to add more than one broadcast url
|
||||
|
@ -76,7 +75,7 @@ export class BroadcastingActivityComponent implements OnInit {
|
|||
*/
|
||||
isPanelOpened: boolean = false;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private broadcastingSub: Subscription;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -99,8 +98,7 @@ export class BroadcastingActivityComponent implements OnInit {
|
|||
* @internal
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.broadcastingSub) this.broadcastingSub.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -148,7 +146,7 @@ export class BroadcastingActivityComponent implements OnInit {
|
|||
}
|
||||
|
||||
private subscribeToBroadcastingStatus() {
|
||||
this.broadcastingService.broadcastingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: BroadcastingStatusInfo | undefined) => {
|
||||
this.broadcastingSub = this.broadcastingService.broadcastingStatusObs.subscribe((event: BroadcastingStatusInfo | undefined) => {
|
||||
if (!!event) {
|
||||
const { status, broadcastingId, error } = event;
|
||||
this.broadcastingStatus = status;
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -72,372 +72,6 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.recording-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.recording-placeholder-img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.recording-status-messages {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.recording-status {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
border: 1px solid var(--ov-warn-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
font-size: 15px;
|
||||
box-shadow: 0 2px 8px 0 rgba(255, 193, 7, 0.04);
|
||||
|
||||
.status-icon {
|
||||
font-size: 28px;
|
||||
color: var(--ov-warn-color);
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.status-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.status-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.status-message {
|
||||
font-size: 14px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-status-starting {
|
||||
background: rgba(255, 193, 7, 0.08);
|
||||
border-color: var(--ov-warn-color);
|
||||
}
|
||||
|
||||
.recording-status-stopping {
|
||||
background: rgba(255, 193, 7, 0.13);
|
||||
border-color: var(--ov-warn-color);
|
||||
}
|
||||
|
||||
.recording-error-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
// Modern recording list styles
|
||||
.recording-list-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-top: 16px;
|
||||
max-height: 500px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-color: var(--ov-accent-action-color);
|
||||
border-radius: 2px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-card {
|
||||
background: var(--ov-surface-background-color);
|
||||
border: 1px solid rgba(0, 102, 204, 0.1);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
padding: 8px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
&.recording-active {
|
||||
background: linear-gradient(135deg, transparent 69%, var(--ov-error-color) 250%);
|
||||
}
|
||||
}
|
||||
|
||||
.recording-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.recording-status-indicator {
|
||||
flex-shrink: 0;
|
||||
padding-top: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
|
||||
&.recording-live {
|
||||
background: #ffffff;
|
||||
box-shadow: 0 0 0 4px var(--ov-error-color);
|
||||
animation: pulse-dot 2s infinite;
|
||||
}
|
||||
|
||||
&.recording-stopping {
|
||||
background: var(--ov-warn-color);
|
||||
animation: pulse-dot 2s infinite;
|
||||
}
|
||||
|
||||
&.recording-failed {
|
||||
background: var(--ov-error-color);
|
||||
}
|
||||
|
||||
&.recording-ready {
|
||||
background: #4caf50;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.recording-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--ov-text-surface-color);
|
||||
margin-bottom: 4px;
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
height: 17px;
|
||||
}
|
||||
|
||||
.recording-status-text {
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
|
||||
&.recording-live-text {
|
||||
color: var(--ov-primary-action-color);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-metadata {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-top: 4px;
|
||||
height: auto;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
font-size: 12px;
|
||||
color: var(--ov-text-surface-color);
|
||||
opacity: 0.7;
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
|
||||
.metadata-icon {
|
||||
font-size: 14px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-actions-menu {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
opacity: 1;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
height: 32px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&.action-play {
|
||||
color: var(--ov-accent-action-color);
|
||||
|
||||
&:hover {
|
||||
background: rgba(0, 102, 204, 0.1);
|
||||
color: var(--ov-accent-action-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.action-view {
|
||||
color: var(--ov-accent-action-color);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
&.action-download {
|
||||
color: #4caf50;
|
||||
|
||||
&:hover {
|
||||
background: rgba(76, 175, 80, 0.1);
|
||||
color: #4caf50;
|
||||
}
|
||||
}
|
||||
|
||||
&.action-delete {
|
||||
color: var(--ov-error-color);
|
||||
|
||||
&:hover {
|
||||
background: rgba(244, 67, 54, 0.1);
|
||||
color: var(--ov-error-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Animations
|
||||
@keyframes pulse-dot {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.2);
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse-border {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-actions {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
}
|
||||
|
||||
// Mobile responsive design for new recording cards
|
||||
@media (max-width: 768px) {
|
||||
.recording-list-container {
|
||||
padding-top: 12px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.recording-card {
|
||||
padding: 8px;
|
||||
height: 100px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.recording-header {
|
||||
gap: 8px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.recording-info {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.recording-metadata {
|
||||
gap: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.metadata-item {
|
||||
font-size: 11px;
|
||||
gap: 2px;
|
||||
|
||||
.metadata-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.recording-actions-menu {
|
||||
opacity: 1; // Always visible on mobile
|
||||
gap: 6px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.recording-message {
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
@ -446,84 +80,14 @@
|
|||
color: var(--ov-error-color);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.recording-error {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
background: rgba(244, 67, 54, 0.08);
|
||||
border: 1px solid var(--ov-error-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
margin: 16px 0;
|
||||
color: var(--ov-error-color);
|
||||
font-size: 15px;
|
||||
box-shadow: 0 2px 8px 0 rgba(244, 67, 54, 0.04);
|
||||
|
||||
.error-icon {
|
||||
font-size: 28px;
|
||||
color: var(--ov-error-color);
|
||||
flex-shrink: 0;
|
||||
margin-top: 2px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.error-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
.recording-name {
|
||||
font-size: 14px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.disable-recording-btn {
|
||||
background-color: var(--ov-secondary-action-color) !important;
|
||||
color: var(--ov-text-surface-color) !important;
|
||||
cursor: not-allowed !important;
|
||||
}
|
||||
|
||||
// Enhanced empty state
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 32px 16px;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
margin-bottom: 16px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 48px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
color: var(--ov-accent-action-color);
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-state-title {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
margin: 0 0 8px 0;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
.empty-state-subtitle {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
opacity: 0.7;
|
||||
line-height: 1.4;
|
||||
.recording-date {
|
||||
font-size: 12px !important;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.not-allowed-message {
|
||||
|
@ -532,44 +96,25 @@
|
|||
}
|
||||
|
||||
.recording-action-buttons {
|
||||
margin: 5px 0px;
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#start-recording-btn {
|
||||
width: 100%;
|
||||
background-color: var(--ov-primary-action-color);
|
||||
color: var(--ov-secondary-action-color);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
#view-recordings-btn {
|
||||
width: 100%;
|
||||
background-color: var(--ov-accent-action-color);
|
||||
color: var(--ov-secondary-action-color);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
margin-bottom: 10px;
|
||||
|
||||
mat-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.start-recording-button-container {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#stop-recording-btn {
|
||||
width: 100%;
|
||||
background-color: var(--ov-error-color);
|
||||
color: var(--ov-secondary-action-color);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
#reset-recording-status-btn {
|
||||
width: 100%;
|
||||
background-color: var(--ov-accent-action-color);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
background-color: var(--ov-secondary-action-color);
|
||||
}
|
||||
|
||||
.recording-item {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, OnDestroy, Output } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import {
|
||||
RecordingDeleteRequestedEvent,
|
||||
RecordingDownloadClickedEvent,
|
||||
|
@ -16,7 +16,6 @@ import { RecordingService } from '../../../../services/recording/recording.servi
|
|||
import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
|
||||
import { ILogger } from '../../../../models/logger.model';
|
||||
import { LoggerService } from '../../../../services/logger/logger.service';
|
||||
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
|
||||
|
||||
/**
|
||||
* The **RecordingActivityComponent** is the component that allows showing the recording activity.
|
||||
|
@ -25,14 +24,13 @@ import { OpenViduComponentsConfigService } from '../../../../services/config/dir
|
|||
selector: 'ov-recording-activity',
|
||||
templateUrl: './recording-activity.component.html',
|
||||
styleUrls: ['./recording-activity.component.scss', '../activities-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
|
||||
// TODO: Allow to add more than one recording type
|
||||
// TODO: Allow to choose where the recording is stored (s3, google cloud, etc)
|
||||
// TODO: Allow to choose the layout of the recording
|
||||
export class RecordingActivityComponent implements OnInit, OnDestroy {
|
||||
export class RecordingActivityComponent implements OnInit {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -68,20 +66,6 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Provides event notifications that fire when view recordings button has been clicked.
|
||||
* This event is triggered when the user wants to view all recordings in an external page.
|
||||
*/
|
||||
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* This event is fired when the user clicks on the view recording button.
|
||||
* It provides the recording ID as event data.
|
||||
*/
|
||||
@Output() onViewRecordingClicked: EventEmitter<string> = new EventEmitter<string>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -114,53 +98,12 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
recordingError: any;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hasRoomTracksPublished: boolean = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
mouseHovering: boolean = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
isReadOnlyMode: boolean = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
viewButtonText: string = 'PANEL.RECORDING.VIEW';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
showStartStopRecordingButton: boolean = true;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
showViewRecordingsButton: boolean = false;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
showRecordingList: boolean = true; // Controls visibility of the recording list in the panel
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
showControls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean } = {
|
||||
play: true,
|
||||
download: true,
|
||||
delete: true,
|
||||
externalView: false
|
||||
};
|
||||
|
||||
private log: ILogger;
|
||||
private destroy$ = new Subject<void>();
|
||||
private recordingStatusSubscription: Subscription;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -171,8 +114,7 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
private actionService: ActionService,
|
||||
private openviduService: OpenViduService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private loggerSrv: LoggerService,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
private loggerSrv: LoggerService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('RecordingActivityComponent');
|
||||
}
|
||||
|
@ -182,23 +124,13 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.subscribeToRecordingStatus();
|
||||
this.subscribeToTracksChanges();
|
||||
this.subscribeToConfigChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
trackByRecordingId(index: number, recording: RecordingInfo): string | undefined {
|
||||
return recording.id;
|
||||
if (this.recordingStatusSubscription) this.recordingStatusSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -293,105 +225,11 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
this.recordingService.playRecording(recording);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
viewRecording(recording: RecordingInfo) {
|
||||
// This method can be overridden or emit a custom event for navigation
|
||||
// For now, it uses the same behavior as play, but can be customized
|
||||
if (!recording.filename) {
|
||||
this.log.e('Error viewing recording. Recording filename is undefined');
|
||||
return;
|
||||
}
|
||||
const payload: RecordingPlayClickedEvent = {
|
||||
roomName: this.openviduService.getRoomName(),
|
||||
recordingId: recording.id
|
||||
};
|
||||
this.onRecordingPlayClicked.emit(payload);
|
||||
// You can customize this to navigate to a different page instead
|
||||
this.recordingService.playRecording(recording);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
viewAllRecordings() {
|
||||
this.onViewRecordingsClicked.emit();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Format duration in seconds to a readable format (e.g., "2m 30s")
|
||||
*/
|
||||
formatDuration(seconds: number): string {
|
||||
if (!seconds || seconds < 0) return '0s';
|
||||
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = Math.floor(seconds % 60);
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}h ${minutes}m`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}m ${remainingSeconds}s`;
|
||||
} else {
|
||||
return `${remainingSeconds}s`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Format file size in bytes to a readable format (e.g., "2.5 MB")
|
||||
*/
|
||||
formatFileSize(bytes: number): string {
|
||||
if (!bytes || bytes < 0) return '0 B';
|
||||
|
||||
const sizes = ['B', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
const size = bytes / Math.pow(1024, i);
|
||||
|
||||
return `${size.toFixed(1)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
private subscribeToConfigChanges() {
|
||||
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
|
||||
this.isReadOnlyMode = readOnly;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.recordingActivityShowControls$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((controls: { play?: boolean; download?: boolean; delete?: boolean; externalView?: boolean }) => {
|
||||
this.showControls = controls;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.recordingActivityStartStopRecordingButton$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
|
||||
this.showStartStopRecordingButton = show;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.recordingActivityViewRecordingsButton$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
|
||||
this.showViewRecordingsButton = show;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.recordingActivityShowRecordingsList$.pipe(takeUntil(this.destroy$)).subscribe((show: boolean) => {
|
||||
this.showRecordingList = show;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToRecordingStatus() {
|
||||
this.recordingService.recordingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: RecordingStatusInfo) => {
|
||||
this.recordingStatusSubscription = this.recordingService.recordingStatusObs.subscribe((event: RecordingStatusInfo) => {
|
||||
const { status, recordingList, error } = event;
|
||||
this.recordingStatus = status;
|
||||
if (this.showRecordingList) {
|
||||
this.recordingList = recordingList;
|
||||
} else {
|
||||
// Avoid showing recordings
|
||||
this.recordingList = [];
|
||||
}
|
||||
this.recordingError = error;
|
||||
this.recordingAlive = this.recordingStatus === RecordingStatus.STARTED;
|
||||
if (this.recordingStatus !== RecordingStatus.FAILED) {
|
||||
|
@ -400,24 +238,4 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
|
|||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToTracksChanges() {
|
||||
this.hasRoomTracksPublished = this.openviduService.hasRoomTracksPublished();
|
||||
|
||||
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
const newValue = this.openviduService.hasRoomTracksPublished();
|
||||
if (this.hasRoomTracksPublished !== newValue) {
|
||||
this.hasRoomTracksPublished = newValue;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.participantService.remoteParticipants$.pipe(takeUntil(this.destroy$)).subscribe(() => {
|
||||
const newValue = this.openviduService.hasRoomTracksPublished();
|
||||
if (this.hasRoomTracksPublished !== newValue) {
|
||||
this.hasRoomTracksPublished = newValue;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,10 @@
|
|||
<div class="panel-container" id="background-effects-container" [class.prejoin-mode]="mode === 'prejoin'">
|
||||
@if (mode === 'meeting') {
|
||||
<div class="panel-container" id="background-effects-container">
|
||||
<div class="panel-header-container">
|
||||
<h3 class="panel-title">{{ 'PANEL.BACKGROUND.TITLE' | translate }}</h3>
|
||||
|
||||
<button class="panel-close-button" mat-icon-button matTooltip="{{ 'PANEL.CLOSE' | translate }}" (click)="close()">
|
||||
<mat-icon>close</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<button class="pansel-close-button" mat-icon-button matTooltip="{{ 'PANEL.CLOSE' | translate }}" (click)="close()">
|
||||
<mat-icon>arrow_back</mat-icon>
|
||||
</button>
|
||||
}
|
||||
|
||||
<div class="effects-container" fxFlex="100%" fxLayoutAlign="space-evenly none">
|
||||
<div>
|
||||
|
|
|
@ -1,11 +1,5 @@
|
|||
.prejoin-mode {
|
||||
margin: 0 10px 0px 10px;
|
||||
max-height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
.background-title {
|
||||
color: var(--ov-text-surface-color);
|
||||
margin: 10px 0;
|
||||
}
|
||||
.effects-container {
|
||||
display: block !important;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { BackgroundEffect, EffectType } from '../../../models/background-effect.model';
|
||||
import { PanelType } from '../../../models/panel.model';
|
||||
|
@ -12,13 +12,9 @@ import { VirtualBackgroundService } from '../../../services/virtual-background/v
|
|||
selector: 'ov-background-effects-panel',
|
||||
templateUrl: './background-effects-panel.component.html',
|
||||
styleUrls: ['../panel.component.scss', './background-effects-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class BackgroundEffectsPanelComponent implements OnInit {
|
||||
@Input() mode: 'prejoin' | 'meeting' = 'meeting';
|
||||
@Output() onClose = new EventEmitter<void>();
|
||||
|
||||
backgroundSelectedId: string;
|
||||
effectType = EffectType;
|
||||
backgroundImages: BackgroundEffect[] = [];
|
||||
|
@ -56,14 +52,14 @@ export class BackgroundEffectsPanelComponent implements OnInit {
|
|||
}
|
||||
|
||||
close() {
|
||||
if (this.mode === 'prejoin') {
|
||||
this.onClose.emit();
|
||||
} else {
|
||||
this.panelService.togglePanel(PanelType.BACKGROUND_EFFECTS);
|
||||
}
|
||||
}
|
||||
|
||||
async applyBackground(effect: BackgroundEffect) {
|
||||
if (effect.type === EffectType.NONE) {
|
||||
await this.backgroundService.removeBackground();
|
||||
} else {
|
||||
await this.backgroundService.applyBackground(effect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ChatMessage } from '../../../models/chat.model';
|
||||
import { PanelType } from '../../../models/panel.model';
|
||||
import { ChatService } from '../../../services/chat/chat.service';
|
||||
|
@ -13,8 +13,7 @@ import { PanelService } from '../../../services/panel/panel.service';
|
|||
selector: 'ov-chat-panel',
|
||||
templateUrl: './chat-panel.component.html',
|
||||
styleUrls: ['../panel.component.scss', './chat-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ChatPanelComponent implements OnInit, AfterViewInit {
|
||||
/**
|
||||
|
@ -34,7 +33,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
*/
|
||||
messageList: ChatMessage[] = [];
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private chatMessageSubscription: Subscription;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
|
@ -66,8 +65,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
* @ignore
|
||||
*/
|
||||
ngOnDestroy(): void {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.chatMessageSubscription) this.chatMessageSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,7 +108,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToMessages() {
|
||||
this.chatService.messagesObs.pipe(takeUntil(this.destroy$)).subscribe((messages: ChatMessage[]) => {
|
||||
this.chatMessageSubscription = this.chatService.messagesObs.subscribe((messages: ChatMessage[]) => {
|
||||
this.messageList = messages;
|
||||
if (this.panelService.isChatPanelOpened()) {
|
||||
this.scrollToBottom();
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
Output,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { skip, Subject, takeUntil } from 'rxjs';
|
||||
import { skip, Subscription } from 'rxjs';
|
||||
import {
|
||||
ActivitiesPanelDirective,
|
||||
AdditionalPanelsDirective,
|
||||
|
@ -25,7 +25,6 @@ import {
|
|||
} from '../../models/panel.model';
|
||||
import { PanelService } from '../../services/panel/panel.service';
|
||||
import { BackgroundEffect } from '../../models/background-effect.model';
|
||||
import { TemplateManagerService, PanelTemplateConfiguration } from '../../services/template/template-manager.service';
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -38,8 +37,7 @@ import { TemplateManagerService, PanelTemplateConfiguration } from '../../servic
|
|||
selector: 'ov-panel',
|
||||
templateUrl: './panel.component.html',
|
||||
styleUrls: ['./panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class PanelComponent implements OnInit {
|
||||
/**
|
||||
|
@ -76,20 +74,42 @@ export class PanelComponent implements OnInit {
|
|||
*/
|
||||
@ContentChild(ParticipantsPanelDirective)
|
||||
set externalParticipantPanel(externalParticipantsPanel: ParticipantsPanelDirective) {
|
||||
this._externalParticipantPanel = externalParticipantsPanel;
|
||||
// This directive will has value only when PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalParticipantsPanel) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.participantsPanelTemplate = externalParticipantsPanel.template;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: backgroundEffectsPanel does not provides customization
|
||||
// @ContentChild(BackgroundEffectsPanelDirective)
|
||||
// set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) {
|
||||
// This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
// if (externalBackgroundEffectsPanel) {
|
||||
// this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template;
|
||||
// }
|
||||
// }
|
||||
|
||||
// TODO: settingsPanel does not provides customization
|
||||
// @ContentChild(SettingsPanelDirective)
|
||||
// set externalSettingsPanel(externalSettingsPanel: SettingsPanelDirective) {
|
||||
// This directive will has value only when SETTINGS PANEL component tagged with '*ovSettingsPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
// if (externalSettingsPanel) {
|
||||
// this.settingsPanelTemplate = externalSettingsPanel.template;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild(ActivitiesPanelDirective)
|
||||
set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) {
|
||||
this._externalActivitiesPanel = externalActivitiesPanel;
|
||||
// This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalActivitiesPanel) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.activitiesPanelTemplate = externalActivitiesPanel.template;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,9 +118,10 @@ export class PanelComponent implements OnInit {
|
|||
*/
|
||||
@ContentChild(ChatPanelDirective)
|
||||
set externalChatPanel(externalChatPanel: ChatPanelDirective) {
|
||||
this._externalChatPanel = externalChatPanel;
|
||||
// This directive will has value only when CHAT PANEL component tagged with '*ovChatPanel'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalChatPanel) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.chatPanelTemplate = externalChatPanel.template;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,9 +130,10 @@ export class PanelComponent implements OnInit {
|
|||
*/
|
||||
@ContentChild(AdditionalPanelsDirective)
|
||||
set externalAdditionalPanels(externalAdditionalPanels: AdditionalPanelsDirective) {
|
||||
this._externalAdditionalPanels = externalAdditionalPanels;
|
||||
// This directive will has value only when ADDITIONAL PANELS component tagged with '*ovPanelAdditionalPanels'
|
||||
// is inside of the PANEL component tagged with '*ovPanel'
|
||||
if (externalAdditionalPanels) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.additionalPanelsTemplate = externalAdditionalPanels.template;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,20 +194,7 @@ export class PanelComponent implements OnInit {
|
|||
* @internal
|
||||
*/
|
||||
isExternalPanelOpened: boolean;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Template configuration managed by the service
|
||||
*/
|
||||
templateConfig: PanelTemplateConfiguration = {};
|
||||
|
||||
// Store directive references for template setup
|
||||
private _externalParticipantPanel?: ParticipantsPanelDirective;
|
||||
private _externalChatPanel?: ChatPanelDirective;
|
||||
private _externalActivitiesPanel?: ActivitiesPanelDirective;
|
||||
private _externalAdditionalPanels?: AdditionalPanelsDirective;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private panelSubscription: Subscription;
|
||||
|
||||
private panelEmitersHandler: Map<
|
||||
PanelType,
|
||||
|
@ -197,78 +206,30 @@ export class PanelComponent implements OnInit {
|
|||
*/
|
||||
constructor(
|
||||
private panelService: PanelService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private templateManagerService: TemplateManagerService
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.setupTemplates();
|
||||
this.subscribeToPanelToggling();
|
||||
this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged);
|
||||
this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged);
|
||||
this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged);
|
||||
this.panelEmitersHandler.set(PanelType.ACTIVITIES, this.onActivitiesPanelStatusChanged);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Sets up all templates using the template manager service
|
||||
*/
|
||||
private setupTemplates(): void {
|
||||
this.templateConfig = this.templateManagerService.setupPanelTemplates(
|
||||
this._externalParticipantPanel,
|
||||
this._externalChatPanel,
|
||||
this._externalActivitiesPanel,
|
||||
this._externalAdditionalPanels
|
||||
);
|
||||
|
||||
// Apply templates to component properties for backward compatibility
|
||||
this.applyTemplateConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Applies the template configuration to component properties
|
||||
*/
|
||||
private applyTemplateConfiguration(): void {
|
||||
if (this.templateConfig.participantsPanelTemplate) {
|
||||
this.participantsPanelTemplate = this.templateConfig.participantsPanelTemplate;
|
||||
}
|
||||
if (this.templateConfig.chatPanelTemplate) {
|
||||
this.chatPanelTemplate = this.templateConfig.chatPanelTemplate;
|
||||
}
|
||||
if (this.templateConfig.activitiesPanelTemplate) {
|
||||
this.activitiesPanelTemplate = this.templateConfig.activitiesPanelTemplate;
|
||||
}
|
||||
if (this.templateConfig.additionalPanelsTemplate) {
|
||||
this.additionalPanelsTemplate = this.templateConfig.additionalPanelsTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Updates templates and triggers change detection
|
||||
*/
|
||||
private updateTemplatesAndMarkForCheck(): void {
|
||||
this.setupTemplates();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.isChatPanelOpened = false;
|
||||
this.isParticipantsPanelOpened = false;
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.panelSubscription) this.panelSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
private subscribeToPanelToggling() {
|
||||
this.panelService.panelStatusObs.pipe(skip(1), takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.panelSubscription = this.panelService.panelStatusObs.pipe(skip(1)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.isChatPanelOpened = ev.isOpened && ev.panelType === PanelType.CHAT;
|
||||
this.isParticipantsPanelOpened = ev.isOpened && ev.panelType === PanelType.PARTICIPANTS;
|
||||
this.isBackgroundEffectsPanelOpened = ev.isOpened && ev.panelType === PanelType.BACKGROUND_EFFECTS;
|
||||
|
|
|
@ -1,71 +1,33 @@
|
|||
<mat-list>
|
||||
<mat-list-item>
|
||||
<!-- Main participant container with improved structure -->
|
||||
<div class="participant-container" [attr.data-participant-id]="_participant?.sid">
|
||||
<!-- Avatar section with dynamic color -->
|
||||
<div
|
||||
class="participant-avatar"
|
||||
[style.background-color]="_participant?.colorProfile"
|
||||
[attr.aria-label]="'Avatar for ' + participantDisplayName"
|
||||
>
|
||||
<div matListItemIcon class="participant-avatar" [style.background-color]="_participant.colorProfile">
|
||||
<mat-icon>person</mat-icon>
|
||||
</div>
|
||||
<h3 matListItemTitle class="participant-name">{{ _participant.name }}
|
||||
<span *ngIf="_participant.isLocal"> ({{ 'PANEL.PARTICIPANTS.YOU' | translate }})</span>
|
||||
</h3>
|
||||
<p matListItemLine class="participant-subtitle">{{ _participant | tracksPublishedTypes }}</p>
|
||||
<!-- <p matListItemLine>
|
||||
<span class="participant-subtitle"></span>
|
||||
</p> -->
|
||||
|
||||
<!-- Content section with name and status -->
|
||||
<div class="participant-content">
|
||||
<div class="participant-name">
|
||||
{{ participantDisplayName }}
|
||||
<span *ngIf="isLocalParticipant" class="local-indicator">
|
||||
{{ 'PANEL.PARTICIPANTS.YOU' | translate }}
|
||||
</span>
|
||||
|
||||
<!-- Participant badges -->
|
||||
<div class="participant-badges">
|
||||
<ng-container *ngTemplateOutlet="participantBadgeTemplate"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="participant-subtitle">
|
||||
<span class="status-indicator">
|
||||
{{ _participant | tracksPublishedTypes }}
|
||||
</span>
|
||||
<!-- Additional status indicators -->
|
||||
<span *ngIf="_participant?.isMutedForcibly" class="status-indicator">
|
||||
<mat-icon>volume_off</mat-icon>
|
||||
{{ 'PANEL.PARTICIPANTS.MUTED' | translate }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action buttons section -->
|
||||
<div class="participant-action-buttons">
|
||||
<!-- Mute/Unmute button for remote participants -->
|
||||
<div class="participant-action-buttons" matListItemMeta>
|
||||
<button
|
||||
mat-icon-button
|
||||
id="mute-btn"
|
||||
*ngIf="!isLocalParticipant && showMuteButton"
|
||||
[class.warn-btn]="_participant?.isMutedForcibly"
|
||||
*ngIf="!_participant.isLocal && showMuteButton"
|
||||
[class.warn-btn]="_participant.isMutedForcibly"
|
||||
(click)="toggleMuteForcibly()"
|
||||
[disabled]="!_participant"
|
||||
[disableRipple]="true"
|
||||
[attr.aria-label]="
|
||||
_participant?.isMutedForcibly
|
||||
? ('PANEL.PARTICIPANTS.UNMUTE' | translate) + ' ' + participantDisplayName
|
||||
: ('PANEL.PARTICIPANTS.MUTE' | translate) + ' ' + participantDisplayName
|
||||
"
|
||||
[matTooltip]="
|
||||
_participant?.isMutedForcibly ? ('PANEL.PARTICIPANTS.UNMUTE' | translate) : ('PANEL.PARTICIPANTS.MUTE' | translate)
|
||||
"
|
||||
>
|
||||
<mat-icon *ngIf="!_participant?.isMutedForcibly">volume_up</mat-icon>
|
||||
<mat-icon *ngIf="_participant?.isMutedForcibly">volume_off</mat-icon>
|
||||
<mat-icon *ngIf="!_participant.isMutedForcibly">volume_up</mat-icon>
|
||||
<mat-icon *ngIf="_participant.isMutedForcibly">volume_off</mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- External item elements with improved structure -->
|
||||
<div class="external-elements" *ngIf="hasExternalElements">
|
||||
<!-- External item elements -->
|
||||
<ng-container *ngIf="participantPanelItemElementsTemplate">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemElementsTemplate"></ng-container>
|
||||
</div>
|
||||
</div>
|
||||
</ng-container>
|
||||
</div>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
|
|
|
@ -1,443 +1,68 @@
|
|||
:host {
|
||||
// Container for the participant item
|
||||
.participant-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--ov-surface-radius, 8px);
|
||||
background-color: var(--ov-surface-background, #ffffff);
|
||||
border-bottom: 1px solid var(--ov-surface-border, #e0e0e0);
|
||||
transition: all 0.2s ease-in-out;
|
||||
min-height: 64px;
|
||||
|
||||
// &:hover {
|
||||
// background-color: var(--ov-surface-hover, #f5f5f5);
|
||||
// transform: translateY(-1px);
|
||||
// box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
// }
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// Loading state
|
||||
&.loading {
|
||||
opacity: 0.7;
|
||||
pointer-events: none;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--ov-primary-color, #1976d2);
|
||||
border-radius: 50%;
|
||||
border-top-color: transparent;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
// Focus state for keyboard navigation
|
||||
&:focus-within {
|
||||
outline: 2px solid var(--ov-primary-color, #1976d2);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Avatar styling with improved design
|
||||
.participant-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: inherit;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
margin-right: 12px;
|
||||
padding: 0;
|
||||
color: #ffffff;
|
||||
font-weight: 500;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
margin: auto !important;
|
||||
padding: 10px;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
// Main content area
|
||||
.participant-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0; // Allows text truncation
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
// Participant name styling
|
||||
.participant-name {
|
||||
font-weight: 600 !important;
|
||||
font-size: 14px;
|
||||
line-height: 1.2;
|
||||
color: var(--ov-text-primary, #212121);
|
||||
margin: 0 0 4px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
// Local participant indicator
|
||||
.local-indicator {
|
||||
font-size: 10px;
|
||||
font-weight: 600;
|
||||
color: var(--ov-primary-color, #1976d2);
|
||||
background-color: var(--ov-primary-light, #e3f2fd);
|
||||
padding: 4px 8px;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
flex-shrink: 0;
|
||||
border: 1px solid var(--ov-primary-color, #1976d2);
|
||||
}
|
||||
}
|
||||
|
||||
// Subtitle styling
|
||||
.participant-subtitle {
|
||||
font-style: normal;
|
||||
font-size: 12px !important;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
font-size: 11px !important;
|
||||
margin: 0;
|
||||
color: var(--ov-text-secondary, #757575);
|
||||
line-height: 1.3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
// Status indicators
|
||||
.status-indicator {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 12px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
.participant-name {
|
||||
font-weight: bold !important;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
// Different colors for different statuses
|
||||
&.camera-on {
|
||||
color: var(--ov-success-color, #4caf50);
|
||||
}
|
||||
|
||||
&.camera-off {
|
||||
color: var(--ov-warning-color, #ff9800);
|
||||
}
|
||||
|
||||
&.microphone-muted {
|
||||
color: var(--ov-error-color, #d32f2f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action buttons container
|
||||
.participant-action-buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
// Mute button styling
|
||||
#mute-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
color: var(--ov-text-secondary, #757575);
|
||||
background-color: transparent;
|
||||
transition: all 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ov-surface-hover, #f5f5f5);
|
||||
color: var(--ov-text-primary, #212121);
|
||||
transform: scale(1.1);
|
||||
::ng-deep .participant-action-buttons > *:not(#mute-btn) {
|
||||
display: contents;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 2px solid var(--ov-primary-color, #1976d2);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.warn-btn {
|
||||
color: var(--ov-error-color, #d32f2f);
|
||||
background-color: var(--ov-error-light, #ffebee);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ov-error-color, #d32f2f);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
// Pulsing animation for muted state
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
// Participant badges container
|
||||
.participant-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
|
||||
// Badge styling
|
||||
::ng-deep .badge {
|
||||
// Badge variants
|
||||
&.moderator {
|
||||
color: var(--ov-warning-color, #f57c00);
|
||||
}
|
||||
|
||||
&.speaker {
|
||||
color: var(--ov-primary-color, #1976d2);
|
||||
}
|
||||
|
||||
&.host {
|
||||
color: var(--ov-success-color, #4caf50);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// After local participant content area
|
||||
.after-local-content {
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid var(--ov-surface-border, #e0e0e0);
|
||||
animation: fadeIn 0.3s ease-in-out;
|
||||
background-color: var(--ov-surface-alt, #fafafa);
|
||||
border-radius: var(--ov-surface-radius, 8px);
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
// External item elements styling
|
||||
.external-elements {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
// Custom styling for external buttons
|
||||
::ng-deep button {
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Material Design overrides for better integration
|
||||
mat-list {
|
||||
padding: 0;
|
||||
::ng-deep .participant-action-buttons > *:not(#mute-btn) > * {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-list-item {
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
min-height: auto !important;
|
||||
border-radius: var(--ov-surface-radius, 8px);
|
||||
height: max-content !important;
|
||||
padding-bottom: 10px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-list-item:hover {
|
||||
color: #000000 !important;
|
||||
}
|
||||
::ng-deep .mat-mdc-list-item:hover .mat-mdc-list-item-title {
|
||||
color: var(--ov-text-surface-color) !important;
|
||||
}
|
||||
|
||||
mat-list {
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-list-item__content {
|
||||
padding: 0 !important;
|
||||
align-self: stretch !important;
|
||||
width: 100%;
|
||||
padding-left: 10px !important;
|
||||
align-self: center !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-list-base {
|
||||
--mdc-list-list-item-hover-label-text-color: unset;
|
||||
--mdc-list-list-item-hover-leading-icon-color: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-list-item:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
// Animations
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
50% {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive design
|
||||
@media (max-width: 768px) {
|
||||
.participant-container {
|
||||
padding: 10px 12px;
|
||||
min-height: 56px;
|
||||
}
|
||||
|
||||
.participant-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
margin-right: 10px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
&::after {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
bottom: 1px;
|
||||
right: 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
font-size: 13px;
|
||||
|
||||
.local-indicator {
|
||||
font-size: 9px;
|
||||
padding: 2px 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.participant-subtitle {
|
||||
font-size: 11px !important;
|
||||
}
|
||||
|
||||
#mute-btn {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
border-radius: 50%;
|
||||
color: var(--ov-text-surface-color);
|
||||
}
|
||||
|
||||
.after-local-content {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
// High contrast mode support
|
||||
@media (prefers-contrast: high) {
|
||||
.participant-container {
|
||||
border: 2px solid var(--ov-text-primary, #212121);
|
||||
}
|
||||
|
||||
.participant-avatar {
|
||||
border: 2px solid var(--ov-surface-background, #ffffff);
|
||||
}
|
||||
|
||||
.local-indicator {
|
||||
border-width: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
// Reduced motion support
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.participant-container,
|
||||
.participant-avatar,
|
||||
#mute-btn,
|
||||
.after-local-content,
|
||||
.external-elements ::ng-deep button {
|
||||
transition: none;
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.participant-container:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.participant-avatar:hover,
|
||||
#mute-btn:hover,
|
||||
.external-elements ::ng-deep button:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#mute-btn.warn-btn {
|
||||
animation: none;
|
||||
}
|
||||
}
|
||||
|
||||
// Dark theme support
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.participant-container {
|
||||
background-color: var(--ov-surface-background, #424242);
|
||||
border-bottom-color: var(--ov-surface-border, #616161);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ov-surface-hover, #484848);
|
||||
}
|
||||
}
|
||||
|
||||
.participant-name {
|
||||
color: var(--ov-text-primary, #ffffff);
|
||||
}
|
||||
|
||||
.participant-subtitle {
|
||||
color: var(--ov-text-secondary, #cccccc);
|
||||
}
|
||||
|
||||
.after-local-content {
|
||||
background-color: var(--ov-surface-alt, #373737);
|
||||
}
|
||||
.warn-btn {
|
||||
/* background-color: var(--ov-error-color) !important; */
|
||||
color: var(--ov-error-color);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,23 +1,21 @@
|
|||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-components-angular.directive';
|
||||
import { ParticipantPanelParticipantBadgeDirective } from '../../../../directives/template/internals.directive';
|
||||
import { ParticipantModel } from '../../../../models/participant.model';
|
||||
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
|
||||
import { ParticipantService } from '../../../../services/participant/participant.service';
|
||||
import { TemplateManagerService, ParticipantPanelItemTemplateConfiguration } from '../../../../services/template/template-manager.service';
|
||||
|
||||
/**
|
||||
*
|
||||
* The **ParticipantPanelItemComponent** is hosted inside of the {@link ParticipantsPanelComponent}.
|
||||
* It displays participant information with enhanced UI/UX, including support for custom content
|
||||
* injection through structural directives.
|
||||
* It is in charge of displaying the participants information inside of the ParticipansPanelComponent.
|
||||
*/
|
||||
|
||||
@Component({
|
||||
selector: 'ov-participant-panel-item',
|
||||
templateUrl: './participant-panel-item.component.html',
|
||||
styleUrls: ['./participant-panel-item.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
|
@ -36,69 +34,40 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
|
|||
*/
|
||||
@ContentChild(ParticipantPanelItemElementsDirective)
|
||||
set externalItemElements(externalItemElements: ParticipantPanelItemElementsDirective) {
|
||||
this._externalItemElements = externalItemElements;
|
||||
// This directive will has value only when ITEM ELEMENTS component tagget with '*ovParticipantPanelItemElements' directive
|
||||
// is inside of the P PANEL ITEM component tagged with '*ovParticipantPanelItem' directive
|
||||
if (externalItemElements) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.participantPanelItemElementsTemplate = externalItemElements.template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The participant to be displayed
|
||||
* @ignore
|
||||
*/
|
||||
@Input()
|
||||
set participant(participant: ParticipantModel) {
|
||||
this._participant = participant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild(ParticipantPanelParticipantBadgeDirective)
|
||||
set externalParticipantBadge(participantBadge: ParticipantPanelParticipantBadgeDirective) {
|
||||
this._externalParticipantBadge = participantBadge;
|
||||
if (participantBadge) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Template configuration managed by the service
|
||||
*/
|
||||
templateConfig: ParticipantPanelItemTemplateConfiguration = {};
|
||||
|
||||
// Store directive references for template setup
|
||||
private _externalItemElements?: ParticipantPanelItemElementsDirective;
|
||||
private _externalParticipantBadge?: ParticipantPanelParticipantBadgeDirective;
|
||||
|
||||
/**
|
||||
* The participant to be displayed
|
||||
*/
|
||||
@Input()
|
||||
set participant(participant: ParticipantModel) {
|
||||
this._participant = participant;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Current participant being displayed
|
||||
*/
|
||||
_participant: ParticipantModel;
|
||||
|
||||
/**
|
||||
* Whether to show the mute button for remote participants
|
||||
*/
|
||||
@Input()
|
||||
muteButton: boolean = true;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
constructor(
|
||||
private libService: OpenViduComponentsConfigService,
|
||||
private participantService: ParticipantService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private templateManagerService: TemplateManagerService
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.setupTemplates();
|
||||
this.subscribeToParticipantPanelItemDirectives();
|
||||
}
|
||||
|
||||
|
@ -110,72 +79,14 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
/**
|
||||
* Toggles the mute state of a remote participant
|
||||
* @ignore
|
||||
*/
|
||||
toggleMuteForcibly() {
|
||||
if (this._participant && !this._participant.isLocal) {
|
||||
if (this._participant) {
|
||||
this.participantService.setRemoteMutedForcibly(this._participant.sid, !this._participant.isMutedForcibly);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template for local participant badge
|
||||
*/
|
||||
get participantBadgeTemplate(): TemplateRef<any> | undefined {
|
||||
return this._externalParticipantBadge?.template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current participant is the local participant
|
||||
*/
|
||||
get isLocalParticipant(): boolean {
|
||||
return this._participant?.isLocal || false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the participant's display name
|
||||
*/
|
||||
get participantDisplayName(): string {
|
||||
return this._participant?.name || '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if external elements are available
|
||||
*/
|
||||
get hasExternalElements(): boolean {
|
||||
return !!this.participantPanelItemElementsTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Sets up all templates using the template manager service
|
||||
*/
|
||||
private setupTemplates(): void {
|
||||
this.templateConfig = this.templateManagerService.setupParticipantPanelItemTemplates(this._externalItemElements);
|
||||
|
||||
// Apply templates to component properties for backward compatibility
|
||||
this.applyTemplateConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Applies the template configuration to component properties
|
||||
*/
|
||||
private applyTemplateConfiguration(): void {
|
||||
if (this.templateConfig.participantPanelItemElementsTemplate) {
|
||||
this.participantPanelItemElementsTemplate = this.templateConfig.participantPanelItemElementsTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Updates templates and triggers change detection
|
||||
*/
|
||||
private updateTemplatesAndMarkForCheck(): void {
|
||||
this.setupTemplates();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
private subscribeToParticipantPanelItemDirectives() {
|
||||
this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => {
|
||||
this.showMuteButton = value;
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
</div>
|
||||
|
||||
<div class="scrollable">
|
||||
|
||||
<div class="local-participant-container" *ngIf="localParticipant">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: localParticipant }"></ng-container>
|
||||
<mat-divider *ngIf="true"></mat-divider>
|
||||
</div>
|
||||
<ng-container *ngTemplateOutlet="participantPanelAfterLocalParticipantTemplate"></ng-container>
|
||||
|
||||
<div class="remote-participants-container" id="remote-participants-container" *ngIf="remoteParticipants.length > 0">
|
||||
|
||||
<div *ngFor="let participant of this.remoteParticipants" id="remote-participant-item">
|
||||
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: participant }"></ng-container>
|
||||
</div>
|
||||
|
|
|
@ -13,10 +13,8 @@ import {
|
|||
import { ParticipantService } from '../../../../services/participant/participant.service';
|
||||
import { PanelService } from '../../../../services/panel/panel.service';
|
||||
import { ParticipantPanelItemDirective } from '../../../../directives/template/openvidu-components-angular.directive';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ParticipantModel } from '../../../../models/participant.model';
|
||||
import { TemplateManagerService, ParticipantsPanelTemplateConfiguration } from '../../../../services/template/template-manager.service';
|
||||
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
|
||||
|
||||
/**
|
||||
* The **ParticipantsPanelComponent** is hosted inside of the {@link PanelComponent}.
|
||||
|
@ -27,8 +25,7 @@ import { OpenViduComponentsConfigService } from '../../../../services/config/dir
|
|||
selector: 'ov-participants-panel',
|
||||
templateUrl: './participants-panel.component.html',
|
||||
styleUrls: ['../../panel.component.scss', './participants-panel.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
/**
|
||||
|
@ -50,33 +47,20 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
|
|||
*/
|
||||
@ContentChild('participantPanelItem', { read: TemplateRef }) participantPanelItemTemplate: TemplateRef<any>;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild('participantPanelAfterLocalParticipant', { read: TemplateRef })
|
||||
participantPanelAfterLocalParticipantTemplate: TemplateRef<any>;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild(ParticipantPanelItemDirective)
|
||||
set externalParticipantPanelItem(externalParticipantPanelItem: ParticipantPanelItemDirective) {
|
||||
this._externalParticipantPanelItem = externalParticipantPanelItem;
|
||||
// This directive will has value only when PARTICIPANT PANEL ITEM component tagged with '*ovParticipantPanelItem'
|
||||
// is inside of the PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
|
||||
if (externalParticipantPanelItem) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.participantPanelItemTemplate = externalParticipantPanelItem.template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Template configuration managed by the service
|
||||
*/
|
||||
templateConfig: ParticipantsPanelTemplateConfiguration = {};
|
||||
|
||||
// Store directive references for template setup
|
||||
private _externalParticipantPanelItem?: ParticipantPanelItemDirective;
|
||||
|
||||
private destroy$ = new Subject<void>();
|
||||
private localParticipantSubs: Subscription;
|
||||
private remoteParticipantsSubs: Subscription;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
|
@ -84,26 +68,32 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
|
|||
constructor(
|
||||
private participantService: ParticipantService,
|
||||
private panelService: PanelService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private templateManagerService: TemplateManagerService,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
ngOnInit(): void {
|
||||
this.setupTemplates();
|
||||
this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => {
|
||||
if (p) {
|
||||
this.localParticipant = p;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeToParticipantsChanges();
|
||||
this.remoteParticipantsSubs = this.participantService.remoteParticipants$.subscribe((p: ParticipantModel[]) => {
|
||||
this.remoteParticipants = p;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
|
||||
if (this.remoteParticipantsSubs) this.remoteParticipantsSubs.unsubscribe;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -118,57 +108,6 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
|
|||
}
|
||||
}
|
||||
|
||||
private subscribeToParticipantsChanges() {
|
||||
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe((p: ParticipantModel | undefined) => {
|
||||
if (p) {
|
||||
this.localParticipant = p;
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.participantService.remoteParticipants$.pipe(takeUntil(this.destroy$)).subscribe((p: ParticipantModel[]) => {
|
||||
this.remoteParticipants = p;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Sets up all templates using the template manager service
|
||||
*/
|
||||
private setupTemplates(): void {
|
||||
this.templateConfig = this.templateManagerService.setupParticipantsPanelTemplates(
|
||||
this._externalParticipantPanelItem,
|
||||
this.defaultParticipantPanelItemTemplate
|
||||
);
|
||||
|
||||
// Apply templates to component properties for backward compatibility
|
||||
this.applyTemplateConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Applies the template configuration to component properties
|
||||
*/
|
||||
private applyTemplateConfiguration(): void {
|
||||
if (this.templateConfig.participantPanelItemTemplate) {
|
||||
this.participantPanelItemTemplate = this.templateConfig.participantPanelItemTemplate;
|
||||
}
|
||||
if (this.templateConfig.participantPanelAfterLocalParticipantTemplate) {
|
||||
this.participantPanelAfterLocalParticipantTemplate = this.templateConfig.participantPanelAfterLocalParticipantTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Updates templates and triggers change detection
|
||||
*/
|
||||
private updateTemplatesAndMarkForCheck(): void {
|
||||
this.setupTemplates();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
|
|
@ -22,27 +22,25 @@
|
|||
[value]="settingsOptions.GENERAL"
|
||||
>
|
||||
<mat-icon matListItemIcon>manage_accounts</mat-icon>
|
||||
<div *ngIf="!isMobile">{{ 'PANEL.SETTINGS.GENERAL' | translate }}</div>
|
||||
<div mat-line *ngIf="!isMobile">{{ 'PANEL.SETTINGS.GENERAL' | translate }}</div>
|
||||
</mat-list-option>
|
||||
<mat-list-option
|
||||
*ngIf="showCameraButton"
|
||||
class="option"
|
||||
id="video-opt"
|
||||
[selected]="selectedOption === settingsOptions.VIDEO"
|
||||
[value]="settingsOptions.VIDEO"
|
||||
>
|
||||
<mat-icon matListItemIcon>videocam</mat-icon>
|
||||
<div *ngIf="!isMobile">{{ 'PANEL.SETTINGS.VIDEO' | translate }}</div>
|
||||
<div mat-line *ngIf="!isMobile">{{ 'PANEL.SETTINGS.VIDEO' | translate }}</div>
|
||||
</mat-list-option>
|
||||
<mat-list-option
|
||||
*ngIf="showMicrophoneButton"
|
||||
class="option"
|
||||
id="audio-opt"
|
||||
[selected]="selectedOption === settingsOptions.AUDIO"
|
||||
[value]="settingsOptions.AUDIO"
|
||||
>
|
||||
<mat-icon matListItemIcon>mic</mat-icon>
|
||||
<div *ngIf="!isMobile">{{ 'PANEL.SETTINGS.AUDIO' | translate }}</div>
|
||||
<div mat-line *ngIf="!isMobile">{{ 'PANEL.SETTINGS.AUDIO' | translate }}</div>
|
||||
</mat-list-option>
|
||||
<!-- <mat-list-option
|
||||
*ngIf="showCaptions"
|
||||
|
@ -70,12 +68,12 @@
|
|||
</mat-list>
|
||||
</div>
|
||||
<ov-video-devices-select
|
||||
*ngIf="showCameraButton && selectedOption === settingsOptions.VIDEO"
|
||||
*ngIf="selectedOption === settingsOptions.VIDEO"
|
||||
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
|
||||
(onVideoEnabledChanged)="onVideoEnabledChanged.emit($event)"
|
||||
></ov-video-devices-select>
|
||||
<ov-audio-devices-select
|
||||
*ngIf="showMicrophoneButton && selectedOption === settingsOptions.AUDIO"
|
||||
*ngIf="selectedOption === settingsOptions.AUDIO"
|
||||
(onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)"
|
||||
(onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)"
|
||||
></ov-audio-devices-select>
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
::ng-deep .lang-selector .expand-more-icon,
|
||||
::ng-deep .lang-selector mat-icon {
|
||||
color: var(--ov-text-surface-color) !important;
|
||||
color: var(--ov-secondary-action-color) !important;
|
||||
}
|
||||
|
||||
::ng-deep .lang-selector div,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { PanelStatusInfo, PanelSettingsOptions, PanelType } from '../../../models/panel.model';
|
||||
import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service';
|
||||
import { PanelService } from '../../../services/panel/panel.service';
|
||||
|
@ -13,8 +13,7 @@ import { LangOption } from '../../../models/lang.model';
|
|||
@Component({
|
||||
selector: 'ov-settings-panel',
|
||||
templateUrl: './settings-panel.component.html',
|
||||
styleUrls: ['../panel.component.scss', './settings-panel.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['../panel.component.scss', './settings-panel.component.scss']
|
||||
})
|
||||
export class SettingsPanelComponent implements OnInit {
|
||||
@Output() onVideoEnabledChanged = new EventEmitter<boolean>();
|
||||
|
@ -24,11 +23,10 @@ export class SettingsPanelComponent implements OnInit {
|
|||
@Output() onLangChanged = new EventEmitter<LangOption>();
|
||||
settingsOptions: typeof PanelSettingsOptions = PanelSettingsOptions;
|
||||
selectedOption: PanelSettingsOptions = PanelSettingsOptions.GENERAL;
|
||||
showCameraButton: boolean = true;
|
||||
showMicrophoneButton: boolean = true;
|
||||
showCaptions: boolean = true;
|
||||
panelSubscription: Subscription;
|
||||
isMobile: boolean = false;
|
||||
private destroy$ = new Subject<void>();
|
||||
private captionsSubs: Subscription;
|
||||
constructor(
|
||||
private panelService: PanelService,
|
||||
private platformService: PlatformService,
|
||||
|
@ -41,8 +39,7 @@ export class SettingsPanelComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.captionsSubs) this.captionsSubs.unsubscribe();
|
||||
}
|
||||
|
||||
close() {
|
||||
|
@ -53,13 +50,13 @@ export class SettingsPanelComponent implements OnInit {
|
|||
}
|
||||
|
||||
private subscribeToDirectives() {
|
||||
this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showCameraButton = value));
|
||||
this.libService.microphoneButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showMicrophoneButton = value));
|
||||
this.libService.captionsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showCaptions = value));
|
||||
this.captionsSubs = this.libService.captionsButton$.subscribe((value: boolean) => {
|
||||
this.showCaptions = value;
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToPanelToggling() {
|
||||
this.panelService.panelStatusObs.pipe(takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.panelSubscription = this.panelService.panelStatusObs.subscribe((ev: PanelStatusInfo) => {
|
||||
if (ev.panelType === PanelType.SETTINGS && !!ev.subOptionType) {
|
||||
this.selectedOption = ev.subOptionType as PanelSettingsOptions;
|
||||
}
|
||||
|
|
|
@ -1,118 +1,64 @@
|
|||
<div class="prejoin-container" id="prejoin-container">
|
||||
<!-- Top Language Toolbar -->
|
||||
<div class="top-toolbar" *ngIf="!isMinimal">
|
||||
<ov-lang-selector [compact]="false" class="language-selector" (onLangChanged)="onLangChanged.emit($event)"> </ov-lang-selector>
|
||||
<div class="container" id="prejoin-container">
|
||||
|
||||
<div *ngIf="isLoading" id="loading-container">
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
<span>{{ 'PREJOIN.PREPARING' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
@if (isLoading) {
|
||||
<div class="loading-overlay">
|
||||
<div class="loading-content">
|
||||
<mat-spinner [diameter]="40"></mat-spinner>
|
||||
<span class="loading-text">{{ 'PREJOIN.PREPARING' | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Main Content -->
|
||||
<div class="prejoin-content">
|
||||
<!-- Main Card -->
|
||||
<div class="prejoin-main">
|
||||
<!-- Video Preview Section -->
|
||||
<div class="video-preview-section">
|
||||
<div class="video-preview-container" [@containerResize]="showBackgroundPanel ? 'compact' : 'normal'">
|
||||
<div class="video-frame">
|
||||
<div *ngIf="!isLoading" id="prejoin-card">
|
||||
<ov-lang-selector *ngIf="!isMinimal" [compact]="true" class="lang-btn" (onLangChanged)="onLangChanged.emit($event)">
|
||||
</ov-lang-selector>
|
||||
|
||||
<div>
|
||||
<div class="video-container">
|
||||
<div id="video-poster">
|
||||
<ov-media-element
|
||||
[track]="videoTrack"
|
||||
[showAvatar]="!videoTrack || videoTrack.isMuted"
|
||||
[avatarName]="participantName"
|
||||
[avatarColor]="'hsl(48, 100%, 50%)'"
|
||||
[isLocal]="true"
|
||||
class="video-element"
|
||||
>
|
||||
</ov-media-element>
|
||||
|
||||
<!-- Video Controls Overlay -->
|
||||
<div class="video-overlay">
|
||||
<div class="device-controls">
|
||||
<div class="control-group" *ngIf="showCameraButton">
|
||||
<ov-video-devices-select
|
||||
[compact]="true"
|
||||
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
|
||||
(onVideoEnabledChanged)="videoEnabledChanged($event)"
|
||||
class="device-selector"
|
||||
>
|
||||
</ov-video-devices-select>
|
||||
></ov-media-element>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-group" *ngIf="showMicrophoneButton">
|
||||
<div class="media-controls-container">
|
||||
<!-- Camera -->
|
||||
<div class="video-controls-container">
|
||||
<ov-video-devices-select
|
||||
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
|
||||
(onVideoEnabledChanged)="videoEnabledChanged($event)"
|
||||
></ov-video-devices-select>
|
||||
</div>
|
||||
|
||||
<!-- Microphone -->
|
||||
<div class="audio-controls-container">
|
||||
<ov-audio-devices-select
|
||||
[compact]="true"
|
||||
(onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)"
|
||||
(onAudioEnabledChanged)="audioEnabledChanged($event)"
|
||||
(onDeviceSelectorClicked)="onDeviceSelectorClicked()"
|
||||
class="device-selector"
|
||||
>
|
||||
</ov-audio-devices-select>
|
||||
</div>
|
||||
></ov-audio-devices-select>
|
||||
</div>
|
||||
|
||||
<!-- Virtual Background Button -->
|
||||
<div class="background-control" *ngIf="backgroundEffectEnabled">
|
||||
<button
|
||||
mat-icon-button
|
||||
class="background-button"
|
||||
(click)="toggleBackgroundPanel()"
|
||||
[matTooltip]="'Virtual Backgrounds'"
|
||||
[disabled]="!isVideoEnabled"
|
||||
>
|
||||
<mat-icon class="material-symbols-outlined">background_replace</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (showBackgroundPanel) {
|
||||
<div class="vb-container" [@slideInOut]>
|
||||
<ov-background-effects-panel [mode]="'prejoin'" (onClose)="closeBackgroundPanel()"> </ov-background-effects-panel>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Configuration Section -->
|
||||
<div class="configuration-section">
|
||||
<!-- Participant Name Input -->
|
||||
<div class="input-section" *ngIf="showParticipantName">
|
||||
<div class="participant-name-container">
|
||||
<ov-participant-name-input
|
||||
[isPrejoinPage]="true"
|
||||
[error]="!!_error"
|
||||
(onNameUpdated)="onParticipantNameChanged($event)"
|
||||
(onEnterPressed)="onEnterPressed()"
|
||||
class="name-input"
|
||||
>
|
||||
</ov-participant-name-input>
|
||||
></ov-participant-name-input>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<div *ngIf="!!_error" class="error-message">
|
||||
<mat-icon class="error-icon">error_outline</mat-icon>
|
||||
<span class="error-text">{{ _error }}</span>
|
||||
<div *ngIf="!!_error" id="token-error">
|
||||
<span class="error">{{ _error }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Join Button -->
|
||||
<div class="join-section">
|
||||
<button
|
||||
mat-flat-button
|
||||
(click)="join()"
|
||||
class="join-button"
|
||||
[disabled]="showParticipantName && !participantName"
|
||||
>
|
||||
<mat-icon class="join-icon">videocam</mat-icon>
|
||||
<div class="join-btn-container">
|
||||
<button mat-flat-button (click)="joinSession()" id="join-button">
|
||||
{{ 'PREJOIN.JOIN' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,411 +1,155 @@
|
|||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
.container {
|
||||
height: 100%;
|
||||
|
||||
.prejoin-container {
|
||||
min-height: 100vh;
|
||||
background: var(--ov-background-color);
|
||||
background-color: var(--ov-background-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#loading-container {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: auto;
|
||||
text-align: -webkit-center;
|
||||
text-align: -moz-center;
|
||||
color: var(--ov-text-primary-color);
|
||||
}
|
||||
|
||||
#prejoin-card {
|
||||
display: grid;
|
||||
align-content: center;
|
||||
margin: auto;
|
||||
// margin-left: 0px;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
width: 70vh;
|
||||
height: 85vh;
|
||||
padding: 20px;
|
||||
box-sizing: border-box;
|
||||
background-color: var(--ov-surface-color);
|
||||
box-shadow: 6px 4px 20px 0px #0003;
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
.prejoin-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
.prejoin-main {
|
||||
max-width: 480px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes slideInFromRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Top Language Toolbar
|
||||
.top-toolbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding: 20px 24px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// Loading State
|
||||
.loading-overlay {
|
||||
::ng-deep .lang-btn {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--ov-background-color, #f5f5f5);
|
||||
z-index: 1000;
|
||||
|
||||
.loading-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.loading-text {
|
||||
color: var(--ov-text-primary-color, #333);
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
height: 25px !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.mat-mdc-progress-spinner {
|
||||
--mdc-circular-progress-active-indicator-color: var(--ov-primary-action-color, #4285f4);
|
||||
}
|
||||
}
|
||||
::ng-deep .lang-btn mat-icon {
|
||||
color: var(--ov-text-surface-color) !important;
|
||||
}
|
||||
|
||||
// Main Content
|
||||
.prejoin-main {
|
||||
.video-container {
|
||||
margin: auto;
|
||||
min-height: 45vh;
|
||||
max-height: 45vh;
|
||||
height: 45vh;
|
||||
max-width: 80%;
|
||||
}
|
||||
#video-poster {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
max-width: 520px;
|
||||
max-height: 544px;
|
||||
background: var(--ov-surface-color, #ffffff);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.media-controls-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
max-width: 80%;
|
||||
margin: auto;
|
||||
height: 25vh;
|
||||
}
|
||||
|
||||
.participant-name-container {
|
||||
display: block !important;
|
||||
width: 100%;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
|
||||
.video-controls-container,
|
||||
.audio-controls-container {
|
||||
width: calc(50% - 3px);
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.join-btn-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#join-button {
|
||||
background-color: var(--ov-primary-action-color);
|
||||
color: var(--ov-secondary-action-color);
|
||||
font-weight: bold;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
// Video Preview Section
|
||||
.video-preview-section {
|
||||
padding: 0;
|
||||
.video-preview-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 4/3;
|
||||
border-radius: var(--ov-surface-radius) var(--ov-surface-radius) 0 0;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
.video-frame {
|
||||
width: 100%;
|
||||
.error {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
color: var(--ov-error-color);
|
||||
}
|
||||
|
||||
/* Styles for screens up to 768px wide */
|
||||
@media (max-width: 768px) {
|
||||
/* Specific styles for small screens */
|
||||
.container {
|
||||
padding: 0px;
|
||||
}
|
||||
#prejoin-card {
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
padding: 0px;
|
||||
}
|
||||
.video-container {
|
||||
height: 50vh;
|
||||
width: 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
.media-controls-container {
|
||||
height: 30vh;
|
||||
width: 90%;
|
||||
max-width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .video-element {
|
||||
width: 100%;
|
||||
/* Styles for screens with horizontal orientation */
|
||||
@media (max-width: 800) and (orientation: landscape) {
|
||||
/* Specific styles for screens in landscape orientation */
|
||||
.container {
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
border-radius: 0;
|
||||
padding: 10px 60px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 16px;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-end;
|
||||
|
||||
.device-controls {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.background-control {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
|
||||
.background-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 16px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: #333333;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
transform: translateZ(0);
|
||||
|
||||
&:active {
|
||||
transform: translateY(-1px);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
&.mat-mdc-button-disabled {
|
||||
background: rgba(255, 255, 255, 0.137);
|
||||
color: rgba(233, 233, 233, 0.5);
|
||||
cursor: not-allowed;
|
||||
|
||||
&:hover {
|
||||
transform: none;
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 22px;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
opacity: 0.9;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
|
||||
&:hover mat-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vb-container {
|
||||
height: fit-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
// Configuration Section
|
||||
.configuration-section {
|
||||
padding: 24px 24px 24px; // Added top padding since video has no padding
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
|
||||
.input-section {
|
||||
::ng-deep .name-input {
|
||||
.mat-mdc-form-field {
|
||||
width: 100%;
|
||||
|
||||
.mat-mdc-text-field-wrapper {
|
||||
border-radius: var(--ov-surface-radius);
|
||||
background-color: var(--ov-input-background, #f8f9fa);
|
||||
border: 1px solid var(--ov-border-color, #e0e0e0);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--ov-primary-action-color, #4285f4);
|
||||
}
|
||||
|
||||
&.mdc-text-field--focused {
|
||||
border-color: var(--ov-primary-action-color, #4285f4);
|
||||
box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-form-field-subscript-wrapper {
|
||||
.prejoin-toolbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: var(--ov-text-primary-color, #333);
|
||||
padding: 16px;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.error-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background-color: rgba(244, 67, 54, 0.08);
|
||||
border: 1px solid rgba(244, 67, 54, 0.2);
|
||||
border-radius: var(--ov-surface-radius);
|
||||
color: var(--ov-error-color, #d32f2f);
|
||||
|
||||
.error-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
.video-controls-container,
|
||||
.audio-controls-container {
|
||||
width: 48%;
|
||||
margin-bottom: 2%;
|
||||
}
|
||||
}
|
||||
|
||||
.join-section {
|
||||
.join-button {
|
||||
width: 100%;
|
||||
height: 56px;
|
||||
background: var(--ov-primary-action-color, #4285f4);
|
||||
color: white;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
box-shadow: 0 2px 8px rgba(66, 133, 244, 0.2);
|
||||
|
||||
&:hover:not([disabled]) {
|
||||
background: var(--ov-primary-action-hover, #3367d6);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 16px rgba(66, 133, 244, 0.3);
|
||||
/* Styles for screens with maximum height of 630px */
|
||||
@media (max-height: 630px) {
|
||||
.video-container {
|
||||
max-width: 85%;
|
||||
height: 37vh;
|
||||
min-height: 37vh;
|
||||
}
|
||||
|
||||
&:active:not([disabled]) {
|
||||
transform: translateY(0);
|
||||
.media-controls-container {
|
||||
height: 35vh;
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background: var(--ov-disabled-color, #ccc);
|
||||
color: var(--ov-disabled-text-color, #999);
|
||||
cursor: not-allowed;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.join-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Responsive Design
|
||||
@media (max-width: 640px) {
|
||||
.prejoin-container {
|
||||
padding: 16px;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.prejoin-main {
|
||||
max-width: 100%;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
.video-preview-section {
|
||||
padding: 0px 0px 12px;
|
||||
|
||||
.video-preview-container {
|
||||
aspect-ratio: 4/3;
|
||||
}
|
||||
}
|
||||
|
||||
.configuration-section {
|
||||
padding: 0 20px 20px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.top-toolbar {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.prejoin-container {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.configuration-section {
|
||||
padding: 0 16px 16px;
|
||||
}
|
||||
|
||||
.video-overlay .device-controls {
|
||||
gap: 8px;
|
||||
|
||||
::ng-deep .device-selector .mat-mdc-icon-button {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-toolbar {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 640px) {
|
||||
.prejoin-container {
|
||||
align-items: flex-start;
|
||||
padding-top: 60px; // Add space for top toolbar
|
||||
}
|
||||
|
||||
.video-preview-section .video-preview-container {
|
||||
aspect-ratio: 4/3; // Keep the taller aspect ratio even on small screens
|
||||
}
|
||||
}
|
||||
|
||||
// Dark theme support
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.prejoin-container {
|
||||
background: linear-gradient(135deg, #1a1a1a 0%, #2d2d2d 100%);
|
||||
}
|
||||
|
||||
.prejoin-main {
|
||||
background: #2d2d2d;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(0, 0, 0, 0.3),
|
||||
0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.configuration-section .input-section ::ng-deep .name-input .participant-name-input-container .input-wrapper {
|
||||
background-color: #3a3a3a;
|
||||
border-color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
// Animation keyframes
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
.prejoin-main {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
transform: translateZ(0);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,5 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
HostListener,
|
||||
Input,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core';
|
||||
import { animate, state, style, transition, trigger } from '@angular/animations';
|
||||
import { filter, Subject, takeUntil, tap } from 'rxjs';
|
||||
import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { ILogger } from '../../models/logger.model';
|
||||
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
||||
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
||||
|
@ -20,6 +9,7 @@ import { TranslateService } from '../../services/translate/translate.service';
|
|||
import { LocalTrack } from 'livekit-client';
|
||||
import { CustomDevice } from '../../models/device.model';
|
||||
import { LangOption } from '../../models/lang.model';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -27,49 +17,7 @@ import { LangOption } from '../../models/lang.model';
|
|||
@Component({
|
||||
selector: 'ov-pre-join',
|
||||
templateUrl: './pre-join.component.html',
|
||||
styleUrls: ['./pre-join.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false,
|
||||
animations: [
|
||||
trigger('containerResize', [
|
||||
state(
|
||||
'normal',
|
||||
style({
|
||||
height: '*'
|
||||
})
|
||||
),
|
||||
state(
|
||||
'compact',
|
||||
style({
|
||||
height: '28vh'
|
||||
})
|
||||
),
|
||||
transition('normal => compact', [animate('250ms cubic-bezier(0.25, 0.8, 0.25, 1)')]),
|
||||
transition('compact => normal', [animate('350ms cubic-bezier(0.25, 0.8, 0.25, 1)')])
|
||||
]),
|
||||
trigger('slideInOut', [
|
||||
transition(':enter', [
|
||||
style({
|
||||
opacity: 0
|
||||
}),
|
||||
animate(
|
||||
'300ms cubic-bezier(0.34, 1.56, 0.64, 1)',
|
||||
style({
|
||||
opacity: 1
|
||||
})
|
||||
)
|
||||
]),
|
||||
transition(':leave', [
|
||||
animate(
|
||||
'200ms cubic-bezier(0.25, 0.46, 0.45, 0.94)',
|
||||
style({
|
||||
opacity: 0,
|
||||
transform: 'translateY(-10px)'
|
||||
})
|
||||
)
|
||||
])
|
||||
])
|
||||
]
|
||||
styleUrls: ['./pre-join.component.scss']
|
||||
})
|
||||
export class PreJoinComponent implements OnInit, OnDestroy {
|
||||
@Input() set error(error: { name: string; message: string } | undefined) {
|
||||
|
@ -83,29 +31,24 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
@Output() onReadyToJoin = new EventEmitter<any>();
|
||||
|
||||
_error: string | undefined;
|
||||
|
||||
windowSize: number;
|
||||
isLoading = true;
|
||||
participantName: string | undefined = '';
|
||||
participantName: string | undefined;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
isMinimal: boolean = false;
|
||||
showCameraButton: boolean = true;
|
||||
showMicrophoneButton: boolean = true;
|
||||
showLogo: boolean = true;
|
||||
showParticipantName: boolean = true;
|
||||
|
||||
// Future feature preparation
|
||||
backgroundEffectEnabled: boolean = true; // Enable virtual backgrounds by default
|
||||
showBackgroundPanel: boolean = false;
|
||||
|
||||
videoTrack: LocalTrack | undefined;
|
||||
audioTrack: LocalTrack | undefined;
|
||||
isVideoEnabled: boolean = false;
|
||||
private tracks: LocalTrack[];
|
||||
private log: ILogger;
|
||||
private destroy$ = new Subject<void>();
|
||||
private screenShareStateSubscription: Subscription;
|
||||
private minimalSub: Subscription;
|
||||
private displayLogoSub: Subscription;
|
||||
private shouldRemoveTracksWhenComponentIsDestroyed: boolean = true;
|
||||
|
||||
@HostListener('window:resize')
|
||||
|
@ -118,6 +61,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
private libService: OpenViduComponentsConfigService,
|
||||
private cdkSrv: CdkOverlayService,
|
||||
private openviduService: OpenViduService,
|
||||
private storageService: StorageService,
|
||||
private translateService: TranslateService,
|
||||
private changeDetector: ChangeDetectorRef
|
||||
) {
|
||||
|
@ -129,28 +73,34 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
await this.initializeDevices();
|
||||
this.windowSize = window.innerWidth;
|
||||
this.isLoading = false;
|
||||
this.changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
// ngAfterContentChecked(): void {
|
||||
// // this.changeDetector.detectChanges();
|
||||
// this.isLoading = false;
|
||||
// }
|
||||
ngAfterContentChecked(): void {
|
||||
this.changeDetector.detectChanges();
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.cdkSrv.setSelector('body');
|
||||
if (this.screenShareStateSubscription) this.screenShareStateSubscription.unsubscribe();
|
||||
if (this.minimalSub) this.minimalSub.unsubscribe();
|
||||
if (this.displayLogoSub) this.displayLogoSub.unsubscribe();
|
||||
|
||||
if (this.shouldRemoveTracksWhenComponentIsDestroyed) {
|
||||
this.tracks?.forEach((track) => {
|
||||
this.tracks.forEach((track) => {
|
||||
track.stop();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async initializeDevices() {
|
||||
await this.initializeDevicesWithRetry();
|
||||
try {
|
||||
this.tracks = await this.openviduService.createLocalTracks();
|
||||
this.openviduService.setLocalTracks(this.tracks);
|
||||
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
||||
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
||||
} catch (error) {
|
||||
this.log.e('Error creating local tracks:', error);
|
||||
}
|
||||
}
|
||||
|
||||
onDeviceSelectorClicked() {
|
||||
|
@ -159,94 +109,47 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
this.cdkSrv.setSelector('#prejoin-container');
|
||||
}
|
||||
|
||||
join() {
|
||||
if (this.showParticipantName && !this.participantName?.trim()) {
|
||||
joinSession() {
|
||||
if (!this.participantName) {
|
||||
this._error = this.translateService.translate('PREJOIN.NICKNAME_REQUIRED');
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any previous errors
|
||||
this._error = undefined;
|
||||
|
||||
// Mark tracks as permanent for avoiding to be removed in ngOnDestroy
|
||||
this.shouldRemoveTracksWhenComponentIsDestroyed = false;
|
||||
|
||||
// Assign participant name to the observable if it is defined
|
||||
if (this.participantName?.trim()) {
|
||||
this.libService.updateGeneralConfig({ participantName: this.participantName.trim() });
|
||||
|
||||
// Wait for the next tick to ensure the participant name propagates
|
||||
// through the observable before emitting onReadyToJoin
|
||||
this.libService.participantName$
|
||||
.pipe(
|
||||
takeUntil(this.destroy$),
|
||||
filter((name) => name === this.participantName?.trim()),
|
||||
tap(() => this.onReadyToJoin.emit())
|
||||
)
|
||||
.subscribe();
|
||||
} else {
|
||||
// No participant name to set, emit immediately
|
||||
this.onReadyToJoin.emit();
|
||||
}
|
||||
}
|
||||
|
||||
onParticipantNameChanged(name: string) {
|
||||
this.participantName = name?.trim() || '';
|
||||
// Clear error when user starts typing
|
||||
if (this._error && this.participantName) {
|
||||
this._error = undefined;
|
||||
}
|
||||
this.participantName = name;
|
||||
}
|
||||
|
||||
onEnterPressed() {
|
||||
this.join();
|
||||
this.joinSession();
|
||||
}
|
||||
|
||||
private subscribeToPrejoinDirectives() {
|
||||
this.libService.minimal$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.minimalSub = this.libService.minimal$.subscribe((value: boolean) => {
|
||||
this.isMinimal = value;
|
||||
this.changeDetector.markForCheck();
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showCameraButton = value;
|
||||
this.changeDetector.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.microphoneButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showMicrophoneButton = value;
|
||||
this.changeDetector.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.displayLogo$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.displayLogoSub = this.libService.displayLogo$.subscribe((value: boolean) => {
|
||||
this.showLogo = value;
|
||||
this.changeDetector.markForCheck();
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((value: string) => {
|
||||
if (value) {
|
||||
this.participantName = value;
|
||||
this.changeDetector.markForCheck();
|
||||
}
|
||||
});
|
||||
|
||||
this.libService.prejoinDisplayParticipantName$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showParticipantName = value;
|
||||
this.changeDetector.markForCheck();
|
||||
this.libService.participantName$.subscribe((value: string) => {
|
||||
if (value) this.participantName = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
async videoEnabledChanged(enabled: boolean) {
|
||||
this.isVideoEnabled = enabled;
|
||||
if (!enabled) {
|
||||
this.closeBackgroundPanel();
|
||||
} else if (!this.videoTrack) {
|
||||
if (enabled && !this.videoTrack) {
|
||||
const newVideoTrack = await this.openviduService.createLocalTracks(true, false);
|
||||
this.videoTrack = newVideoTrack[0];
|
||||
this.tracks.push(this.videoTrack);
|
||||
this.openviduService.setLocalTracks(this.tracks);
|
||||
}
|
||||
|
||||
this.onVideoEnabledChanged.emit(enabled);
|
||||
}
|
||||
|
||||
|
@ -259,68 +162,4 @@ export class PreJoinComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
this.onAudioEnabledChanged.emit(enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle virtual background panel visibility with smooth animation
|
||||
*/
|
||||
toggleBackgroundPanel() {
|
||||
// Add a small delay to ensure smooth transition
|
||||
if (!this.showBackgroundPanel) {
|
||||
// Opening panel
|
||||
this.showBackgroundPanel = true;
|
||||
this.changeDetector.markForCheck();
|
||||
} else {
|
||||
// Closing panel - add slight delay for smooth animation
|
||||
setTimeout(() => {
|
||||
this.showBackgroundPanel = false;
|
||||
this.changeDetector.markForCheck();
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close virtual background panel with smooth animation
|
||||
*/
|
||||
closeBackgroundPanel() {
|
||||
// Add animation delay for smooth closing
|
||||
setTimeout(() => {
|
||||
this.showBackgroundPanel = false;
|
||||
this.changeDetector.markForCheck();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enhanced error handling with better UX
|
||||
*/
|
||||
private handleError(error: any) {
|
||||
this.log.e('PreJoin component error:', error);
|
||||
this._error = error.message || 'An unexpected error occurred';
|
||||
this.changeDetector.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* Improved device initialization with error handling
|
||||
*/
|
||||
private async initializeDevicesWithRetry(maxRetries: number = 3): Promise<void> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
this.tracks = await this.openviduService.createLocalTracks();
|
||||
this.openviduService.setLocalTracks(this.tracks);
|
||||
this.videoTrack = this.tracks.find((track) => track.kind === 'video');
|
||||
this.audioTrack = this.tracks.find((track) => track.kind === 'audio');
|
||||
this.isVideoEnabled = this.openviduService.isVideoTrackEnabled();
|
||||
|
||||
return; // Success, exit retry loop
|
||||
} catch (error) {
|
||||
this.log.w(`Device initialization attempt ${attempt} failed:`, error);
|
||||
|
||||
if (attempt === maxRetries) {
|
||||
this.handleError(error);
|
||||
} else {
|
||||
// Wait before retrying
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { ILogger } from '../../models/logger.model';
|
||||
import { animate, style, transition, trigger } from '@angular/animations';
|
||||
import { MatDrawerContainer, MatSidenav } from '@angular/material/sidenav';
|
||||
import { skip, Subject, takeUntil } from 'rxjs';
|
||||
import { skip, Subscription } from 'rxjs';
|
||||
import { SidenavMode } from '../../models/layout.model';
|
||||
import { PanelStatusInfo, PanelType } from '../../models/panel.model';
|
||||
import { DataTopic } from '../../models/data-topic.model';
|
||||
|
@ -46,9 +46,8 @@ import {
|
|||
RoomEvent,
|
||||
Track
|
||||
} from 'livekit-client';
|
||||
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
|
||||
import { RecordingStatus } from '../../models/recording.model';
|
||||
import { TemplateManagerService, SessionTemplateConfiguration } from '../../services/template/template-manager.service';
|
||||
import { ParticipantModel } from '../../models/participant.model';
|
||||
import { ServiceConfigService } from '../../services/config/service-config.service';
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -59,43 +58,22 @@ import { TemplateManagerService, SessionTemplateConfiguration } from '../../serv
|
|||
templateUrl: './session.component.html',
|
||||
styleUrls: ['./session.component.scss'],
|
||||
animations: [trigger('sessionAnimation', [transition(':enter', [style({ opacity: 0 }), animate('50ms', style({ opacity: 1 }))])])],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class SessionComponent implements OnInit, OnDestroy {
|
||||
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
|
||||
@ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>;
|
||||
@ContentChild('layout', { read: TemplateRef }) layoutTemplate: TemplateRef<any>;
|
||||
/**
|
||||
* Provides event notifications that fire when Room is created for the local participant.
|
||||
* Provides event notifications that fire when OpenVidu Room is created.
|
||||
*
|
||||
*/
|
||||
@Output() onRoomCreated: EventEmitter<Room> = new EventEmitter<Room>();
|
||||
|
||||
/**
|
||||
* Provides event notifications that fire when Room is being reconnected for the local participant.
|
||||
* Provides event notifications that fire when local participant is created.
|
||||
*/
|
||||
@Output() onRoomReconnecting: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Provides event notifications that fire when Room is reconnected for the local participant.
|
||||
*/
|
||||
@Output() onRoomReconnected: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Provides event notifications that fire when participant is disconnected from Room.
|
||||
* @deprecated Use {@link SessionComponent.onParticipantLeft} instead.
|
||||
*/
|
||||
@Output() onRoomDisconnected: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* Provides event notifications that fire when local participant is connected to the Room.
|
||||
*/
|
||||
@Output() onParticipantConnected: EventEmitter<ParticipantModel> = new EventEmitter<ParticipantModel>();
|
||||
|
||||
/**
|
||||
* This event is emitted when the local participant leaves the room.
|
||||
*/
|
||||
@Output() onParticipantLeft: EventEmitter<ParticipantLeftEvent> = new EventEmitter<ParticipantLeftEvent>();
|
||||
@Output() onParticipantCreated: EventEmitter<ParticipantModel> = new EventEmitter<ParticipantModel>();
|
||||
|
||||
room: Room;
|
||||
sideMenu: MatSidenav;
|
||||
|
@ -104,20 +82,17 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
drawer: MatDrawerContainer;
|
||||
loading: boolean = true;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Template configuration managed by the service
|
||||
*/
|
||||
templateConfig: SessionTemplateConfiguration = {};
|
||||
|
||||
private shouldDisconnectRoomWhenComponentIsDestroyed: boolean = true;
|
||||
private readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
|
||||
private destroy$ = new Subject<void>();
|
||||
private menuSubscription: Subscription;
|
||||
private layoutWidthSubscription: Subscription;
|
||||
private updateLayoutInterval: NodeJS.Timeout;
|
||||
private captionLanguageSubscription: Subscription;
|
||||
private log: ILogger;
|
||||
private layoutService: LayoutService;
|
||||
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private serviceConfig: ServiceConfigService,
|
||||
private actionService: ActionService,
|
||||
private openviduService: OpenViduService,
|
||||
private participantService: ParticipantService,
|
||||
|
@ -130,16 +105,15 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
private translateService: TranslateService,
|
||||
// private captionService: CaptionService,
|
||||
private backgroundService: VirtualBackgroundService,
|
||||
private cd: ChangeDetectorRef,
|
||||
private templateManagerService: TemplateManagerService
|
||||
private cd: ChangeDetectorRef
|
||||
) {
|
||||
this.log = this.loggerSrv.get('SessionComponent');
|
||||
this.setupTemplates();
|
||||
this.layoutService = this.serviceConfig.getLayoutService();
|
||||
}
|
||||
|
||||
@HostListener('window:beforeunload')
|
||||
beforeunloadHandler() {
|
||||
this.disconnectRoom(ParticipantLeftReason.BROWSER_UNLOAD);
|
||||
this.disconnectRoom();
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
|
@ -188,39 +162,15 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
set layoutContainer(container: ElementRef) {
|
||||
setTimeout(async () => {
|
||||
if (container) {
|
||||
if (this.libService.showBackgroundEffectsButton()) {
|
||||
// Apply background from storage when layout container is in DOM only when background effects button is enabled
|
||||
// Apply background from storage when layout container is in DOM
|
||||
await this.backgroundService.applyBackgroundFromStorage();
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
|
||||
async ngOnInit() {
|
||||
this.shouldDisconnectRoomWhenComponentIsDestroyed = true;
|
||||
|
||||
// Check if room is available before proceeding
|
||||
if (!this.openviduService.isRoomInitialized()) {
|
||||
this.log.e('Room is not initialized when SessionComponent starts. This indicates a timing issue.');
|
||||
this.actionService.openDialog(
|
||||
this.translateService.translate('ERRORS.SESSION'),
|
||||
'Room is not ready. Please ensure the token is properly configured.'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get room instance
|
||||
try {
|
||||
this.room = this.openviduService.getRoom();
|
||||
this.log.d('Room successfully obtained for SessionComponent');
|
||||
} catch (error) {
|
||||
this.log.e('Unexpected error getting room:', error);
|
||||
this.actionService.openDialog(
|
||||
this.translateService.translate('ERRORS.SESSION'),
|
||||
'Failed to get room instance: ' + (error?.message || error)
|
||||
);
|
||||
return;
|
||||
}
|
||||
this.onRoomCreated.emit(this.room);
|
||||
|
||||
// this.subscribeToCaptionLanguage();
|
||||
this.subcribeToActiveSpeakersChanged();
|
||||
|
@ -233,22 +183,19 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
// this.subscribeToParticipantNameChanged();
|
||||
this.subscribeToDataMessage();
|
||||
this.subscribeToReconnection();
|
||||
this.subscribeToVirtualBackground();
|
||||
|
||||
// if (this.libService.isRecordingEnabled()) {
|
||||
if (this.libService.isRecordingEnabled()) {
|
||||
// this.subscribeToRecordingEvents();
|
||||
// }
|
||||
}
|
||||
|
||||
// if (this.libService.isBroadcastingEnabled()) {
|
||||
if (this.libService.isBroadcastingEnabled()) {
|
||||
// this.subscribeToBroadcastingEvents();
|
||||
// }
|
||||
}
|
||||
try {
|
||||
await this.participantService.connect();
|
||||
// Send room created after participant connect for avoiding to send incomplete room payload
|
||||
this.onRoomCreated.emit(this.room);
|
||||
this.cd.markForCheck();
|
||||
this.loading = false;
|
||||
this.onParticipantConnected.emit(this.participantService.getLocalParticipant());
|
||||
this.onParticipantCreated.emit(this.participantService.getLocalParticipant());
|
||||
} catch (error) {
|
||||
this.log.e('There was an error connecting to the room:', error.code, error.message);
|
||||
this.actionService.openDialog(this.translateService.translate('ERRORS.SESSION'), error?.error || error?.message || error);
|
||||
|
@ -260,41 +207,22 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Sets up all templates using the template manager service
|
||||
*/
|
||||
private setupTemplates(): void {
|
||||
this.templateConfig = this.templateManagerService.setupSessionTemplates(
|
||||
this.toolbarTemplate,
|
||||
this.panelTemplate,
|
||||
this.layoutTemplate
|
||||
);
|
||||
}
|
||||
|
||||
async ngOnDestroy() {
|
||||
if (this.shouldDisconnectRoomWhenComponentIsDestroyed) {
|
||||
await this.disconnectRoom(ParticipantLeftReason.LEAVE);
|
||||
await this.disconnectRoom();
|
||||
}
|
||||
if (this.room) this.room.removeAllListeners();
|
||||
if(this.room) this.room.removeAllListeners();
|
||||
this.participantService.clear();
|
||||
// this.room = undefined;
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.menuSubscription) this.menuSubscription.unsubscribe();
|
||||
if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
|
||||
// if (this.captionLanguageSubscription) this.captionLanguageSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
async disconnectRoom(reason: ParticipantLeftReason) {
|
||||
async disconnectRoom() {
|
||||
// Mark session as disconnected for avoiding to do it again in ngOnDestroy
|
||||
this.shouldDisconnectRoomWhenComponentIsDestroyed = false;
|
||||
await this.openviduService.disconnectRoom(() => {
|
||||
this.onParticipantLeft.emit({
|
||||
roomName: this.openviduService.getRoomName(),
|
||||
participantName: this.participantService.getLocalParticipant()?.name || '',
|
||||
identity: this.participantService.getLocalParticipant()?.identity || '',
|
||||
reason
|
||||
});
|
||||
}, false);
|
||||
await this.openviduService.disconnectRoom();
|
||||
}
|
||||
|
||||
private subscribeToTogglingMenu() {
|
||||
|
@ -311,7 +239,7 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
this.startUpdateLayoutInterval();
|
||||
});
|
||||
|
||||
this.panelService.panelStatusObs.pipe(skip(1), takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.menuSubscription = this.panelService.panelStatusObs.pipe(skip(1)).subscribe((ev: PanelStatusInfo) => {
|
||||
if (this.sideMenu) {
|
||||
this.settingsPanelOpened = ev.isOpened && ev.panelType === PanelType.SETTINGS;
|
||||
|
||||
|
@ -330,7 +258,7 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private subscribeToLayoutWidth() {
|
||||
this.layoutService.layoutWidthObs.pipe(takeUntil(this.destroy$)).subscribe((width) => {
|
||||
this.layoutWidthSubscription = this.layoutService.layoutWidthObs.subscribe((width) => {
|
||||
this.sidenavMode = width <= this.SIDENAV_WIDTH_LIMIT_MODE ? SidenavMode.OVER : SidenavMode.SIDE;
|
||||
});
|
||||
}
|
||||
|
@ -448,7 +376,7 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
this.log.d(`Data event received: ${topic}`);
|
||||
switch (topic) {
|
||||
case DataTopic.CHAT:
|
||||
const participantName = participant?.name || 'Unknown';
|
||||
const participantName = participant?.identity || 'Unknown';
|
||||
this.chatService.addRemoteMessage(event.message, participantName);
|
||||
break;
|
||||
case DataTopic.RECORDING_STARTING:
|
||||
|
@ -500,12 +428,9 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
case DataTopic.ROOM_STATUS:
|
||||
const { recordingList, isRecordingStarted, isBroadcastingStarted, broadcastingId } = event as RoomStatusData;
|
||||
|
||||
if (this.libService.showRecordingActivityRecordingsList()) {
|
||||
this.recordingService.setRecordingList(recordingList);
|
||||
}
|
||||
if (isRecordingStarted) {
|
||||
const recordingActive = recordingList.find((recording) => recording.status === RecordingStatus.STARTED);
|
||||
this.recordingService.setRecordingStarted(recordingActive);
|
||||
this.recordingService.setRecordingStarted();
|
||||
}
|
||||
if (isBroadcastingStarted) {
|
||||
this.broadcastingService.setBroadcastingStarted(broadcastingId);
|
||||
|
@ -518,84 +443,28 @@ export class SessionComponent implements OnInit, OnDestroy {
|
|||
);
|
||||
}
|
||||
|
||||
private subscribeToReconnection() {
|
||||
subscribeToReconnection() {
|
||||
this.room.on(RoomEvent.Reconnecting, () => {
|
||||
this.log.w('Connection lost: Reconnecting');
|
||||
this.actionService.openConnectionDialog(
|
||||
this.translateService.translate('ERRORS.CONNECTION'),
|
||||
this.translateService.translate('ERRORS.RECONNECT')
|
||||
);
|
||||
this.onRoomReconnecting.emit();
|
||||
});
|
||||
this.room.on(RoomEvent.Reconnected, () => {
|
||||
this.log.w('Connection lost: Reconnected');
|
||||
this.actionService.closeConnectionDialog();
|
||||
this.onRoomReconnected.emit();
|
||||
});
|
||||
|
||||
this.room.on(RoomEvent.Disconnected, async (reason: DisconnectReason | undefined) => {
|
||||
this.shouldDisconnectRoomWhenComponentIsDestroyed = false;
|
||||
this.actionService.closeConnectionDialog();
|
||||
const participantLeftEvent: ParticipantLeftEvent = {
|
||||
roomName: this.openviduService.getRoomName(),
|
||||
participantName: this.participantService.getLocalParticipant()?.name || '',
|
||||
identity: this.participantService.getLocalParticipant()?.identity || '',
|
||||
reason: ParticipantLeftReason.NETWORK_DISCONNECT
|
||||
};
|
||||
const messageErrorKey = 'ERRORS.DISCONNECT';
|
||||
let descriptionErrorKey = '';
|
||||
switch (reason) {
|
||||
case DisconnectReason.CLIENT_INITIATED:
|
||||
// Skip disconnect reason if a default disconnect method has been called
|
||||
if (!this.openviduService.shouldHandleClientInitiatedDisconnectEvent) return;
|
||||
participantLeftEvent.reason = ParticipantLeftReason.LEAVE;
|
||||
break;
|
||||
case DisconnectReason.DUPLICATE_IDENTITY:
|
||||
participantLeftEvent.reason = ParticipantLeftReason.DUPLICATE_IDENTITY;
|
||||
descriptionErrorKey = 'ERRORS.DUPLICATE_IDENTITY';
|
||||
break;
|
||||
case DisconnectReason.SERVER_SHUTDOWN:
|
||||
descriptionErrorKey = 'ERRORS.SERVER_SHUTDOWN';
|
||||
participantLeftEvent.reason = ParticipantLeftReason.SERVER_SHUTDOWN;
|
||||
break;
|
||||
case DisconnectReason.PARTICIPANT_REMOVED:
|
||||
participantLeftEvent.reason = ParticipantLeftReason.PARTICIPANT_REMOVED;
|
||||
descriptionErrorKey = 'ERRORS.PARTICIPANT_REMOVED';
|
||||
break;
|
||||
case DisconnectReason.ROOM_DELETED:
|
||||
participantLeftEvent.reason = ParticipantLeftReason.ROOM_DELETED;
|
||||
descriptionErrorKey = 'ERRORS.ROOM_DELETED';
|
||||
break;
|
||||
case DisconnectReason.SIGNAL_CLOSE:
|
||||
participantLeftEvent.reason = ParticipantLeftReason.SIGNAL_CLOSE;
|
||||
descriptionErrorKey = 'ERRORS.SIGNAL_CLOSE';
|
||||
break;
|
||||
default:
|
||||
participantLeftEvent.reason = ParticipantLeftReason.OTHER;
|
||||
descriptionErrorKey = 'ERRORS.DISCONNECT';
|
||||
break;
|
||||
}
|
||||
|
||||
this.log.d('Participant disconnected', participantLeftEvent);
|
||||
this.onParticipantLeft.emit(participantLeftEvent);
|
||||
this.onRoomDisconnected.emit();
|
||||
if (this.libService.getShowDisconnectionDialog() && descriptionErrorKey) {
|
||||
this.actionService.openDialog(
|
||||
this.translateService.translate(messageErrorKey),
|
||||
this.translateService.translate(descriptionErrorKey)
|
||||
if (reason === DisconnectReason.SERVER_SHUTDOWN) {
|
||||
this.log.e('Room Disconnected', reason);
|
||||
this.actionService.openConnectionDialog(
|
||||
this.translateService.translate('ERRORS.CONNECTION'),
|
||||
this.translateService.translate('ERRORS.RECONNECT')
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToVirtualBackground() {
|
||||
this.libService.backgroundEffectsButton$.subscribe(async (enable) => {
|
||||
if (!enable && this.backgroundService.isBackgroundApplied()) {
|
||||
await this.backgroundService.removeBackground();
|
||||
if (this.panelService.isBackgroundEffectsPanelOpened()) {
|
||||
this.panelService.closePanel();
|
||||
}
|
||||
}
|
||||
// await this.disconnectRoom();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,81 +1,55 @@
|
|||
<div class="audio-device-selector" [class.compact]="compact">
|
||||
<!-- Unified Device Button (Compact Mode) -->
|
||||
@if (compact) {
|
||||
<div class="unified-device-button">
|
||||
<!-- Main toggle button -->
|
||||
<div class="device-container-element" [class.mute-btn]="!isMicrophoneEnabled">
|
||||
<!-- <button mat-stroked-button [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger" id="audio-devices-menu">
|
||||
<mat-icon class="audio-icon">mic</mat-icon>
|
||||
<span class="device-label"> {{ microphoneSelected.label }} </span>
|
||||
<mat-icon iconPositionEnd class="chevron-icon">
|
||||
{{ menuTrigger.menuOpen ? 'expand_less' : 'expand_more' }}
|
||||
</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item *ngFor="let microphone of microphones">{{ microphone.label }}</button>
|
||||
</mat-menu> -->
|
||||
<mat-form-field id="audio-devices-form" *ngIf="microphones.length > 0">
|
||||
<mat-select
|
||||
[disabled]="!hasAudioDevices"
|
||||
[compareWith]="compareObjectDevices"
|
||||
[value]="microphoneSelected"
|
||||
(selectionChange)="onMicrophoneSelected($event)"
|
||||
>
|
||||
<mat-select-trigger>
|
||||
<button
|
||||
mat-flat-button
|
||||
class="toggle-section"
|
||||
id="microphone-button"
|
||||
[disableRipple]="true"
|
||||
[disabled]="!hasAudioDevices || microphoneStatusChanging"
|
||||
[class.device-enabled]="isMicrophoneEnabled"
|
||||
[class.device-disabled]="!isMicrophoneEnabled"
|
||||
[class.mute-btn]="!isMicrophoneEnabled"
|
||||
(click)="toggleMic($event)"
|
||||
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
|
||||
[matTooltipDisabled]="!hasAudioDevices"
|
||||
>
|
||||
<mat-icon>{{ isMicrophoneEnabled ? 'mic' : 'mic_off' }}</mat-icon>
|
||||
<mat-icon *ngIf="isMicrophoneEnabled" id="mic"> mic </mat-icon>
|
||||
<mat-icon *ngIf="!isMicrophoneEnabled" id="mic_off"> mic_off </mat-icon>
|
||||
</button>
|
||||
|
||||
<!-- Dropdown section -->
|
||||
@if (microphones.length > 1 && isMicrophoneEnabled) {
|
||||
<button mat-flat-button class="dropdown-section" [matMenuTriggerFor]="microphoneMenu" [disabled]="microphoneStatusChanging">
|
||||
<mat-icon>expand_more</mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Normal Mode - Input Style Selector -->
|
||||
<div class="normal-device-selector">
|
||||
<!-- Input-style Device Selector -->
|
||||
<div class="device-input-selector" [class.disabled]="!hasAudioDevices || !isMicrophoneEnabled">
|
||||
<!-- When microphone is enabled -->
|
||||
@if (isMicrophoneEnabled) {
|
||||
<div class="device-input-selector">
|
||||
<button
|
||||
mat-flat-button
|
||||
class="selector-button"
|
||||
[disabled]="microphoneStatusChanging || microphones.length <= 1"
|
||||
[matMenuTriggerFor]="microphoneMenu"
|
||||
[attr.aria-expanded]="false"
|
||||
<span class="selected-text" *ngIf="!isMicrophoneEnabled">{{ 'PANEL.SETTINGS.DISABLED_AUDIO' | translate }}</span>
|
||||
<span class="selected-text" *ngIf="isMicrophoneEnabled"> {{ microphoneSelected.label }} </span>
|
||||
</mat-select-trigger>
|
||||
<mat-option
|
||||
*ngFor="let microphone of microphones"
|
||||
[disabled]="!isMicrophoneEnabled"
|
||||
[value]="microphone"
|
||||
id="option-{{ microphone.label }}"
|
||||
>
|
||||
<mat-icon class="device-icon">mic</mat-icon>
|
||||
<span class="selected-device-name">{{ microphoneSelected?.label || 'No microphone selected' }}</span>
|
||||
<mat-icon class="dropdown-icon" *ngIf="microphones.length > 1">expand_more</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- When microphone is disabled -->
|
||||
<div class="device-input-selector disabled">
|
||||
<div class="selector-button disabled">
|
||||
<mat-icon class="device-icon">mic_off</mat-icon>
|
||||
<span class="selected-device-name">
|
||||
{{ !hasAudioDevices ? ('PREJOIN.NO_AUDIO_DEVICE' | translate) : 'Microphone disabled' }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<!-- Device Selection Menu (Shared) -->
|
||||
<mat-menu #microphoneMenu="matMenu" class="device-menu">
|
||||
@for (microphone of microphones; track microphone.device) {
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onMicrophoneSelected({ value: microphone })"
|
||||
[class.selected]="microphone.device === microphoneSelected.device"
|
||||
>
|
||||
<mat-icon *ngIf="microphone.device === microphoneSelected.device">check</mat-icon>
|
||||
<span>{{ microphone.label }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
{{ microphone.label }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- No Microphone Available -->
|
||||
@if (microphones.length === 0) {
|
||||
<div class="no-device-message">
|
||||
<mat-icon class="warning-icon">warning</mat-icon>
|
||||
<span>{{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }}</span>
|
||||
<div id="audio-devices-form" *ngIf="microphones.length === 0">
|
||||
<div id="mat-select-trigger">
|
||||
<button mat-icon-button id="microphone-button" class="mute-btn" [disabled]="true">
|
||||
<mat-icon id="mic_off"> mic_off </mat-icon>
|
||||
</button>
|
||||
<span id="audio-devices-not-found"> {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,29 +1,103 @@
|
|||
@use '../device-selector-shared' as shared;
|
||||
$ov-selection-color-btn: #afafaf;
|
||||
$ov-selection-color: #cccccc;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.device-container-element {
|
||||
border-radius: var(--ov-surface-radius);
|
||||
border: 1px solid $ov-selection-color-btn;
|
||||
}
|
||||
.device-container-element.mute-btn {
|
||||
border: 1px solid var(--ov-error-color);
|
||||
}
|
||||
#audio-devices-form {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.audio-device-selector {
|
||||
@include shared.device-selector-base();
|
||||
#audio-devices-not-found {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
// Audio-specific overrides for normal mode
|
||||
&:not(.compact) {
|
||||
.normal-device-selector {
|
||||
.device-input-selector {
|
||||
&:not(.disabled) {
|
||||
.selector-button {
|
||||
// Audio-specific hover effect (simpler than video)
|
||||
&:hover:not([disabled]) {
|
||||
border-color: var(--ov-primary-action-color, #4285f4);
|
||||
#microphone-button {
|
||||
color:#000000
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper,
|
||||
::ng-deep .mat-mdc-form-field-flex,
|
||||
::ng-deep .mat-mdc-select-trigger {
|
||||
height: 50px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field-subscript-wrapper {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
padding-left: 0px;
|
||||
padding-right: 10px;
|
||||
background-color: $ov-selection-color !important;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
::ng-deep .mdc-button--unelevated {
|
||||
border-top-left-radius: var(--ov-surface-radius);
|
||||
border-bottom-left-radius: var(--ov-surface-radius);
|
||||
border-bottom-right-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background-color: $ov-selection-color-btn !important;
|
||||
width: 48px !important;
|
||||
min-width: 48px !important;
|
||||
padding: 0;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-unelevated-button > .mat-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 24px !important;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
padding: 0px !important;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.selected-text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.mat-icon {
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-button-touch-target {
|
||||
border-radius: var(--ov-surface-radius) !important;
|
||||
}
|
||||
|
||||
.mute-btn {
|
||||
color: #ffffff !important;
|
||||
background-color: var(--ov-error-color) !important;
|
||||
}
|
||||
}
|
||||
::ng-deep .mat-mdc-select-panel {
|
||||
background-color: #ffffff !important;
|
||||
}
|
||||
|
||||
// Include shared device menu styles
|
||||
@include shared.device-menu-styles();
|
||||
::ng-deep .mat-mdc-option {
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow {
|
||||
color: var(--ov-primary-action-color) !important;
|
||||
}
|
||||
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::after {
|
||||
border-bottom-color: var(--ov-primary-action-color) !important;
|
||||
}
|
||||
::ng-deep .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled):not(.mat-mdc-option-multiple) {
|
||||
background-color: $ov-selection-color !important;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CustomDevice } from '../../../models/device.model';
|
||||
import { DeviceService } from '../../../services/device/device.service';
|
||||
|
@ -12,11 +12,9 @@ import { ParticipantModel } from '../../../models/participant.model';
|
|||
@Component({
|
||||
selector: 'ov-audio-devices-select',
|
||||
templateUrl: './audio-devices.component.html',
|
||||
styleUrls: ['./audio-devices.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./audio-devices.component.scss']
|
||||
})
|
||||
export class AudioDevicesComponent implements OnInit, OnDestroy {
|
||||
@Input() compact: boolean = false;
|
||||
@Output() onAudioDeviceChanged = new EventEmitter<CustomDevice>();
|
||||
@Output() onAudioEnabledChanged = new EventEmitter<boolean>();
|
||||
|
||||
|
|
|
@ -23,11 +23,11 @@ export class CaptionsSettingComponent implements OnInit, OnDestroy {
|
|||
private captionsStatusSubs: Subscription;
|
||||
private sttStatusSubs: Subscription;
|
||||
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private captionService: CaptionService,
|
||||
private openviduService: OpenViduService
|
||||
) {}
|
||||
private layoutService: LayoutService;
|
||||
|
||||
constructor(private serviceConfig: ServiceConfigService, private captionService: CaptionService, private openviduService: OpenViduService) {
|
||||
this.layoutService = this.serviceConfig.getLayoutService();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// this.isOpenViduPro = this.openviduService.isOpenViduPro();
|
||||
|
|
|
@ -1,246 +0,0 @@
|
|||
// Shared styles for device selectors (video and audio)
|
||||
// This file contains common styling for both video-devices and audio-devices components
|
||||
|
||||
@mixin device-selector-base() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
// Compact Mode - Unified Button
|
||||
&.compact {
|
||||
.unified-device-button {
|
||||
display: flex;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.toggle-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 50px;
|
||||
width: 50px;
|
||||
height: 48px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
transition: all 0.2s ease;
|
||||
|
||||
&.device-enabled {
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
|
||||
mat-icon {
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
}
|
||||
}
|
||||
|
||||
&.device-disabled {
|
||||
background: rgba(244, 67, 54, 0.9);
|
||||
color: white;
|
||||
|
||||
mat-icon {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
&[disabled] {
|
||||
background: rgba(150, 150, 150, 0.5);
|
||||
color: rgba(150, 150, 150, 0.8);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 20px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 30px;
|
||||
width: 30px;
|
||||
height: 48px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
border-left: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0;
|
||||
padding: 0;
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
transition: all 0.2s ease;
|
||||
|
||||
mat-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal Mode - Input Style Selector
|
||||
&:not(.compact) {
|
||||
.normal-device-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
|
||||
.device-input-selector {
|
||||
flex: 1;
|
||||
|
||||
&:not(.disabled) {
|
||||
.selector-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
background: var(--ov-surface-color, #ffffff);
|
||||
border: 2px solid var(--ov-border-color, #e0e0e0);
|
||||
border-radius: 8px;
|
||||
color: var(--ov-text-surface-color);
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
text-align: left;
|
||||
justify-content: flex-start;
|
||||
|
||||
&[disabled] {
|
||||
background: var(--ov-disabled-background, #f5f5f5);
|
||||
color: var(--ov-disabled-text-color, #999);
|
||||
cursor: not-allowed;
|
||||
border-color: var(--ov-disabled-border-color, #ddd);
|
||||
}
|
||||
|
||||
.device-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.selected-device-name {
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dropdown-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
flex-shrink: 0;
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
&[aria-expanded='true'] .dropdown-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
.selector-button.disabled {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
height: 48px;
|
||||
padding: 0 16px;
|
||||
background: var(--ov-disabled-background, #f5f5f5);
|
||||
border: 2px solid var(--ov-disabled-border-color, #ddd);
|
||||
border-radius: 8px;
|
||||
color: var(--ov-disabled-text-color, #999);
|
||||
font-size: 14px;
|
||||
cursor: not-allowed;
|
||||
|
||||
.device-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--ov-error-color, #d32f2f);
|
||||
}
|
||||
|
||||
.selected-device-name {
|
||||
flex: 1;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-device-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
background: rgba(255, 193, 7, 0.1);
|
||||
border: 1px solid rgba(255, 193, 7, 0.3);
|
||||
border-radius: 8px;
|
||||
color: var(--ov-warning-color, #ff9800);
|
||||
font-size: 12px;
|
||||
|
||||
.warning-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shared device menu styles
|
||||
@mixin device-menu-styles() {
|
||||
::ng-deep .device-menu.mat-mdc-menu-panel {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid var(--ov-border-color, #e0e0e0);
|
||||
overflow: hidden;
|
||||
background-color: var(--ov-surface-color);
|
||||
|
||||
.mat-mdc-menu-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
transition: background-color 0.2s ease;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ov-hover-color, #f5f5f5);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(66, 133, 244, 0.08);
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
|
||||
mat-icon {
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
}
|
||||
}
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,33 +1,18 @@
|
|||
<div class="language-selector-container">
|
||||
@if (compact) {
|
||||
<!-- Compact version (icon only) -->
|
||||
<button mat-icon-button [matMenuTriggerFor]="langMenu" class="compact-lang-button" [matTooltip]="'Change language'" disableRipple>
|
||||
<button id="lang-btn-compact" *ngIf="compact" mat-icon-button [matMenuTriggerFor]="menu">
|
||||
<mat-icon>translate</mat-icon>
|
||||
</button>
|
||||
} @else {
|
||||
<!-- Full version (with text) -->
|
||||
<button mat-flat-button [matMenuTriggerFor]="langMenu" class="full-lang-button">
|
||||
<!-- <mat-icon class="lang-icon">translate</mat-icon> -->
|
||||
<span class="lang-name">
|
||||
{{ langSelected?.name }}
|
||||
<mat-icon class="expand-icon">expand_more</mat-icon>
|
||||
</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Language Menu -->
|
||||
<mat-menu #langMenu="matMenu" class="language-menu">
|
||||
@for (lang of languages; track lang.lang) {
|
||||
</button>
|
||||
<button *ngIf="!compact" mat-flat-button [matMenuTriggerFor]="menu" class="lang-button" id="lang-btn">
|
||||
<span id="lang-selected-name">{{ langSelected?.name }}</span>
|
||||
<mat-icon class="expand-more-icon">expand_more</mat-icon>
|
||||
</button>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button
|
||||
mat-menu-item
|
||||
*ngFor="let lang of languages"
|
||||
(click)="onLangSelected(lang.lang)"
|
||||
[attr.id]="'lang-opt-' + lang.lang"
|
||||
[class.selected]="langSelected?.lang === lang.lang"
|
||||
class="language-option"
|
||||
class="lang-menu-opt"
|
||||
>
|
||||
<mat-icon *ngIf="langSelected?.lang === lang.lang" class="check-icon">check</mat-icon>
|
||||
<span class="lang-option-name">{{ lang.name }}</span>
|
||||
<span>{{ lang.name }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
</div>
|
||||
</mat-menu>
|
||||
|
|
|
@ -1,113 +1,21 @@
|
|||
:host {
|
||||
display: inline-block;
|
||||
$ov-surface-color-lighter: color-mix(in srgb, var(--ov-surface-color), #fff 5%);
|
||||
|
||||
.language-selector-container {
|
||||
.compact-lang-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--ov-border-color, #e0e0e0);
|
||||
border-radius: 10px;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
.lang-button {
|
||||
background-color: var(--ov-primary-action-color) !important;
|
||||
color: var(--ov-secondary-action-color) !important;
|
||||
}
|
||||
.lang-button .mat-icon {
|
||||
color: var(--ov-secondary-action-color);
|
||||
|
||||
mat-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
::ng-deep .mat-mdc-menu-panel {
|
||||
border-radius: var(--ov-surface-radius) !important;
|
||||
background-color: $ov-surface-color-lighter !important;
|
||||
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2) !important;
|
||||
}
|
||||
|
||||
.full-lang-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 16px;
|
||||
background: var(--ov-surface-color, #ffffff);
|
||||
border: 2px solid var(--ov-border-color, #e0e0e0);
|
||||
border-radius: 12px;
|
||||
transition: all 0.2s ease;
|
||||
color: var(--ov-text-primary-color, #333);
|
||||
font-weight: 500;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--ov-primary-action-color, #4285f4);
|
||||
box-shadow: 0 2px 8px rgba(66, 133, 244, 0.1);
|
||||
}
|
||||
|
||||
.lang-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--ov-text-surface-color, #666);
|
||||
}
|
||||
|
||||
.lang-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
display: inline-block !important;
|
||||
::ng-deep .mat-mdc-menu-item,
|
||||
.mat-mdc-menu-item:visited,
|
||||
.mat-mdc-menu-item:link {
|
||||
color: var(--ov-text-surface-color) !important;
|
||||
}
|
||||
|
||||
.expand-icon {
|
||||
font-size: 16px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
&[aria-expanded='true'] .expand-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .language-menu.mat-mdc-menu-panel {
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
||||
border: 1px solid var(--ov-border-color, #e0e0e0);
|
||||
overflow: hidden;
|
||||
background: var(--ov-surface-color, #ffffff);
|
||||
|
||||
.language-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
transition: background-color 0.2s ease;
|
||||
font-size: 14px;
|
||||
min-height: 48px;
|
||||
color: var(--ov-text-surface-color);
|
||||
|
||||
&:hover {
|
||||
background-color: var(--ov-hover-color, #f5f5f5);
|
||||
}
|
||||
|
||||
&.selected {
|
||||
background-color: rgba(66, 133, 244, 0.08);
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
|
||||
.check-icon {
|
||||
color: var(--ov-primary-action-color, #4285f4);
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
font-size: 18px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
.lang-option-name {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&.selected .lang-option-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,8 +12,7 @@ import { Subscription } from 'rxjs';
|
|||
@Component({
|
||||
selector: 'ov-lang-selector',
|
||||
templateUrl: './lang-selector.component.html',
|
||||
styleUrls: ['./lang-selector.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./lang-selector.component.scss']
|
||||
})
|
||||
export class LangSelectorComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
|
|
|
@ -1,17 +1,20 @@
|
|||
<div class="participant-name-input-container" [class.error]="error">
|
||||
<div class="input-wrapper">
|
||||
<mat-icon class="input-icon">person</mat-icon>
|
||||
<div id="name-input-container" [ngClass]="{ warn: !name }">
|
||||
<mat-form-field id="name-form" [ngClass]="{ error: error }">
|
||||
<mat-select-trigger>
|
||||
<button mat-flat-button disabled>
|
||||
<mat-icon>person</mat-icon>
|
||||
</button>
|
||||
</mat-select-trigger>
|
||||
<input
|
||||
id="name-input"
|
||||
matInput
|
||||
(change)="updateName()"
|
||||
type="text"
|
||||
maxlength="20"
|
||||
[(ngModel)]="name"
|
||||
autocomplete="off"
|
||||
[disabled]="!isPrejoinPage"
|
||||
(change)="updateName()"
|
||||
(keypress)="eventKeyPress($event)"
|
||||
[placeholder]="'PREJOIN.NICKNAME' | translate"
|
||||
class="name-input-field"
|
||||
/>
|
||||
</div>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
|
|
@ -1,71 +1,67 @@
|
|||
$ov-selection-color-btn: #afafaf;
|
||||
$ov-selection-color: #cccccc;
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
#name-input-container {
|
||||
height: 70px;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
|
||||
.participant-name-input-container {
|
||||
#name-input-container mat-form-field {
|
||||
width: 100%;
|
||||
color: var(--ov-secondary-action-color);
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--ov-input-background, #f8f9fa);
|
||||
border: 2px solid var(--ov-border-color, #e0e0e0);
|
||||
border-radius: 12px;
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
display: inline-flex;
|
||||
padding: 0px !important;
|
||||
}
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
padding: 0;
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
&:focus-within {
|
||||
border-color: var(--ov-primary-action-color, #4285f4);
|
||||
box-shadow: 0 0 0 3px rgba(66, 133, 244, 0.1);
|
||||
height: 70px;
|
||||
background-color: $ov-selection-color !important;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
|
||||
border: 0px !important;
|
||||
}
|
||||
::ng-deep .mdc-button--unelevated {
|
||||
border-top-left-radius: var(--ov-surface-radius) !important;
|
||||
border-bottom-left-radius: var(--ov-surface-radius) !important;
|
||||
border-bottom-right-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background-color: $ov-selection-color-btn !important;
|
||||
width: 48px !important;
|
||||
min-width: 48px !important;
|
||||
padding: 0;
|
||||
height: 70px;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 56px;
|
||||
background: var(--ov-surface-secondary, #f0f0f0);
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
font-size: 20px;
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.name-input-field {
|
||||
flex: 1;
|
||||
height: 56px;
|
||||
padding: 0 16px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
|
||||
&::placeholder {
|
||||
color: var(--ov-text-secondary-color, #666);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--ov-disabled-background, #f5f5f5);
|
||||
color: var(--ov-disabled-text-color, #999);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.error {
|
||||
::ng-deep .mdc-button--unelevated {
|
||||
background-color: var(--ov-error-color) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.error .input-wrapper {
|
||||
border-color: var(--ov-error-color, #d32f2f);
|
||||
box-shadow: 0 0 0 3px rgba(211, 47, 47, 0.1);
|
||||
::ng-deep .mat-mdc-unelevated-button > .mat-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 24px !important;
|
||||
margin: auto;
|
||||
color: #000000 !important;
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
background: rgba(211, 47, 47, 0.1);
|
||||
color: var(--ov-error-color, #d32f2f);
|
||||
}
|
||||
}
|
||||
input {
|
||||
padding-left: 10px !important;
|
||||
border-top-right-radius: var(--ov-surface-radius) !important;
|
||||
border-bottom-right-radius: var(--ov-surface-radius) !important;
|
||||
border-bottom-left-radius: 0px !important;
|
||||
border-top-left-radius: 0px !important;
|
||||
border: 1px solid $ov-selection-color-btn;
|
||||
color: #000000;
|
||||
caret-color: #000000 !important;
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow {
|
||||
color: var(--ov-primary-action-color) !important;
|
||||
}
|
||||
|
|
|
@ -9,8 +9,7 @@ import { StorageService } from '../../../services/storage/storage.service';
|
|||
@Component({
|
||||
selector: 'ov-participant-name-input',
|
||||
templateUrl: './participant-name-input.component.html',
|
||||
styleUrls: ['./participant-name-input.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./participant-name-input.component.scss']
|
||||
})
|
||||
export class ParticipantNameInputComponent implements OnInit {
|
||||
name: string;
|
||||
|
|
|
@ -1,72 +1,40 @@
|
|||
<div class="video-device-selector" [class.compact]="compact">
|
||||
<!-- Unified Device Button (Compact Mode) -->
|
||||
@if (compact) {
|
||||
<div class="unified-device-button">
|
||||
<!-- Main toggle button -->
|
||||
<div class="device-container-element" [class.mute-btn]="!isCameraEnabled">
|
||||
<mat-form-field id="video-devices-form" *ngIf="cameras.length > 0">
|
||||
<mat-select
|
||||
[disabled]="!hasVideoDevices"
|
||||
[compareWith]="compareObjectDevices"
|
||||
[value]="cameraSelected"
|
||||
(selectionChange)="onCameraSelected($event)"
|
||||
>
|
||||
<mat-select-trigger id="mat-select-trigger">
|
||||
<button
|
||||
mat-flat-button
|
||||
class="toggle-section"
|
||||
id="camera-button"
|
||||
[disableRipple]="true"
|
||||
[disabled]="!hasVideoDevices || cameraStatusChanging"
|
||||
[class.device-enabled]="isCameraEnabled"
|
||||
[class.device-disabled]="!isCameraEnabled"
|
||||
[class.mute-btn]="!isCameraEnabled"
|
||||
(click)="toggleCam($event)"
|
||||
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
|
||||
[matTooltipDisabled]="!hasVideoDevices"
|
||||
>
|
||||
<mat-icon>{{ isCameraEnabled ? 'videocam' : 'videocam_off' }}</mat-icon>
|
||||
<mat-icon *ngIf="isCameraEnabled" id="videocam"> videocam </mat-icon>
|
||||
<mat-icon *ngIf="!isCameraEnabled" id="videocam_off"> videocam_off </mat-icon>
|
||||
</button>
|
||||
<span class="selected-text" *ngIf="!isCameraEnabled"> {{ 'PANEL.SETTINGS.DISABLED_VIDEO' | translate }} </span>
|
||||
<span class="selected-text" *ngIf="isCameraEnabled"> {{ cameraSelected.label }} </span>
|
||||
</mat-select-trigger>
|
||||
<mat-option *ngFor="let camera of cameras" [disabled]="!isCameraEnabled" [value]="camera" id="option-{{ camera.label }}">
|
||||
{{ camera.label }}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
|
||||
<!-- Dropdown section -->
|
||||
@if (isCameraEnabled && cameras.length > 1) {
|
||||
<button mat-flat-button class="dropdown-section" [matMenuTriggerFor]="cameraMenu" [disabled]="cameraStatusChanging">
|
||||
<mat-icon>expand_more</mat-icon>
|
||||
<div id="video-devices-form" *ngIf="cameras.length === 0">
|
||||
<div id="mat-select-trigger">
|
||||
<button mat-icon-button id="camera-button" class="mute-btn" [disabled]="true">
|
||||
<mat-icon id="videocam_off"> videocam_off </mat-icon>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Normal Mode - Input-style Selector -->
|
||||
<div class="normal-device-selector">
|
||||
<!-- Device Selector (Input Style) -->
|
||||
@if (isCameraEnabled) {
|
||||
<div class="device-input-selector">
|
||||
<button
|
||||
mat-flat-button
|
||||
class="selector-button"
|
||||
[matMenuTriggerFor]="cameraMenu"
|
||||
[disabled]="cameraStatusChanging || cameras.length <= 1"
|
||||
>
|
||||
<mat-icon class="device-icon">videocam</mat-icon>
|
||||
<span class="selected-device-name">{{ cameraSelected?.label || 'No camera selected' }}</span>
|
||||
<mat-icon class="dropdown-icon" *ngIf="cameras.length > 1">expand_more</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
} @else {
|
||||
<!-- Disabled state message -->
|
||||
<div class="device-input-selector disabled">
|
||||
<div class="selector-button disabled">
|
||||
<mat-icon class="device-icon">videocam_off</mat-icon>
|
||||
<span class="selected-device-name">{{ 'PANEL.SETTINGS.DISABLED_VIDEO' | translate }}</span>
|
||||
<span id="video-devices-not-found"> {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} </span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- Device Selection Menu (Shared) -->
|
||||
<mat-menu #cameraMenu="matMenu" class="device-menu">
|
||||
@for (camera of cameras; track camera.device) {
|
||||
<button mat-menu-item (click)="onCameraSelected({ value: camera })" [class.selected]="camera.device === cameraSelected?.device">
|
||||
<mat-icon *ngIf="camera.device === cameraSelected?.device" class="check-icon">check</mat-icon>
|
||||
<span>{{ camera.label }}</span>
|
||||
</button>
|
||||
}
|
||||
</mat-menu>
|
||||
|
||||
<!-- No Camera Available -->
|
||||
@if (cameras.length === 0) {
|
||||
<div class="no-device-message">
|
||||
<mat-icon class="warning-icon">warning</mat-icon>
|
||||
<span>{{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -1,51 +1,106 @@
|
|||
@use '../device-selector-shared' as shared;
|
||||
|
||||
$ov-selection-color-btn: #afafaf;
|
||||
$ov-selection-color: #cccccc;
|
||||
|
||||
:host {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.video-device-selector {
|
||||
@include shared.device-selector-base();
|
||||
|
||||
// Video-specific overrides for compact mode
|
||||
&.compact {
|
||||
.unified-device-button {
|
||||
.toggle-section {
|
||||
display: flex-end; // Video-specific styling
|
||||
.device-container-element {
|
||||
border-radius: var(--ov-surface-radius);
|
||||
border: 1px solid $ov-selection-color-btn;
|
||||
}
|
||||
.device-container-element.mute-btn {
|
||||
border: 1px solid var(--ov-error-color);
|
||||
}
|
||||
#video-devices-form {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
// Video-specific overrides for normal mode
|
||||
&:not(.compact) {
|
||||
.normal-device-selector {
|
||||
.device-input-selector {
|
||||
&:not(.disabled) {
|
||||
.selector-button {
|
||||
// Video-specific hover effect with box-shadow
|
||||
&:hover:not([disabled]) {
|
||||
background-color: white !important;
|
||||
border-color: var(--ov-primary-action-color);
|
||||
#video-devices-not-found {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
#camera-button {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper,
|
||||
::ng-deep .mat-mdc-form-field-flex,
|
||||
::ng-deep .mat-mdc-select-trigger {
|
||||
height: 50px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field-subscript-wrapper {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-text-field-wrapper {
|
||||
padding-left: 0px;
|
||||
padding-right: 10px;
|
||||
background-color: $ov-selection-color !important;
|
||||
border-radius: var(--ov-surface-radius);
|
||||
}
|
||||
::ng-deep .mdc-button--unelevated {
|
||||
border-top-left-radius: var(--ov-surface-radius);
|
||||
border-bottom-left-radius: var(--ov-surface-radius);
|
||||
border-bottom-right-radius: 0px !important;
|
||||
border-top-right-radius: 0px !important;
|
||||
background-color: $ov-selection-color-btn !important;
|
||||
width: 48px !important;
|
||||
min-width: 48px !important;
|
||||
padding: 0;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-unelevated-button > .mat-icon {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
font-size: 24px !important;
|
||||
margin: auto;
|
||||
}
|
||||
::ng-deep .mat-mdc-form-field-infix {
|
||||
padding: 0px !important;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.selected-text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.mat-icon {
|
||||
vertical-align: middle;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
|
||||
border: 0px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-button-touch-target {
|
||||
border-radius: var(--ov-surface-radius) !important;
|
||||
}
|
||||
|
||||
.mute-btn {
|
||||
color: #ffffff !important;
|
||||
background-color: var(--ov-error-color) !important;
|
||||
}
|
||||
}
|
||||
::ng-deep .mat-mdc-select-panel {
|
||||
background-color: var(--ov-surface-color) !important;
|
||||
}
|
||||
|
||||
// Include shared device menu styles
|
||||
@include shared.device-menu-styles();
|
||||
::ng-deep .mat-mdc-select-panel {
|
||||
background-color: #e2e2e2 !important;
|
||||
}
|
||||
|
||||
// Video-specific additional styles
|
||||
::ng-deep .mat-mdc-option {
|
||||
padding: 10px 10px !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow {
|
||||
color: var(--ov-primary-action-color-lighter) !important;
|
||||
}
|
||||
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::after {
|
||||
border-bottom-color: var(--ov-primary-action-color-lighter) !important;
|
||||
}
|
||||
|
||||
::ng-deep .mat-mdc-option.mdc-list-item--selected:not(.mdc-list-item--disabled):not(.mat-mdc-option-multiple) {
|
||||
background-color: $ov-selection-color !important;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CustomDevice } from '../../../models/device.model';
|
||||
import { DeviceService } from '../../../services/device/device.service';
|
||||
|
@ -12,11 +12,9 @@ import { ParticipantModel } from '../../../models/participant.model';
|
|||
@Component({
|
||||
selector: 'ov-video-devices-select',
|
||||
templateUrl: './video-devices.component.html',
|
||||
styleUrls: ['./video-devices.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./video-devices.component.scss']
|
||||
})
|
||||
export class VideoDevicesComponent implements OnInit, OnDestroy {
|
||||
@Input() compact: boolean = false;
|
||||
@Output() onVideoDeviceChanged = new EventEmitter<CustomDevice>();
|
||||
@Output() onVideoEnabledChanged = new EventEmitter<boolean>();
|
||||
|
||||
|
|
|
@ -48,10 +48,10 @@
|
|||
mat-icon-button
|
||||
id="pin-btn"
|
||||
(click)="toggleVideoPinned()"
|
||||
[class.active-btn]="_track.isPinned"
|
||||
[matTooltip]="_track.isPinned ? ('STREAM.UNPIN' | translate) : ('STREAM.PIN' | translate)"
|
||||
>
|
||||
<mat-icon *ngIf="_track.isPinned" fontSet="material-symbols-outlined" fontIcon="keep">keep_off</mat-icon>
|
||||
<mat-icon *ngIf="!_track.isPinned" id="status-pinned" fontIcon="push_pin"></mat-icon>
|
||||
<mat-icon>push_pin</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!_track.participant.isLocal"
|
||||
|
|
|
@ -17,7 +17,7 @@ With the following directives you can modify the default User Interface with the
|
|||
<!-- start-dynamic-api-directives-content -->
|
||||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **displayAudioDetection** | `boolean` | [StreamDisplayAudioDetectionDirective](../directives/StreamDisplayAudioDetectionDirective.html) |
|
||||
| **displayParticipantName** | `boolean` | [StreamDisplayParticipantNameDirective](../directives/StreamDisplayParticipantNameDirective.html) |
|
||||
| **displayAudioDetection** | `boolean` | [StreamDisplayAudioDetectionDirective](../directives/StreamDisplayAudioDetectionDirective.html) |
|
||||
| **videoControls** | `boolean` | [StreamVideoControlsDirective](../directives/StreamVideoControlsDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
$ov-video-elements-bg-color: var(--ov-primary-action-color);
|
||||
$ov-video-elements-bg-color: var(--ov-primary-action-color);;
|
||||
:host {
|
||||
/* Fixes layout bug. The OV_root is created with the entire layout width and it has a weird UX behaviour */
|
||||
.no-size {
|
||||
|
@ -41,6 +41,9 @@ $ov-video-elements-bg-color: var(--ov-primary-action-color);
|
|||
}
|
||||
}
|
||||
|
||||
.active-btn {
|
||||
color: var(--ov-accent-action-color) !important;
|
||||
}
|
||||
.muted-btn {
|
||||
color: var(--ov-error-color) !important;
|
||||
}
|
||||
|
@ -76,15 +79,11 @@ $ov-video-elements-bg-color: var(--ov-primary-action-color);
|
|||
left: 0;
|
||||
line-height: 0;
|
||||
|
||||
#status-mic,
|
||||
#status-muted-forcibly,
|
||||
#status-pinned {
|
||||
font-size: 24px;
|
||||
margin: 5px;
|
||||
}
|
||||
#status-mic,
|
||||
#status-muted-forcibly {
|
||||
color: var(--ov-error-color);
|
||||
font-size: 24px;
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||
import { MatMenuPanel, MatMenuTrigger } from '@angular/material/menu';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
||||
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
|
||||
import { LayoutService } from '../../services/layout/layout.service';
|
||||
import { ParticipantService } from '../../services/participant/participant.service';
|
||||
import { Track } from 'livekit-client';
|
||||
import { ParticipantTrackPublication } from '../../models/participant.model';
|
||||
import { ServiceConfigService } from '../../services/config/service-config.service';
|
||||
|
||||
/**
|
||||
* The **StreamComponent** is hosted inside of the {@link LayoutComponent}.
|
||||
|
@ -15,8 +16,7 @@ import { ParticipantTrackPublication } from '../../models/participant.model';
|
|||
@Component({
|
||||
selector: 'ov-stream',
|
||||
templateUrl: './stream.component.html',
|
||||
styleUrls: ['./stream.component.scss'],
|
||||
standalone: false
|
||||
styleUrls: ['./stream.component.scss']
|
||||
})
|
||||
export class StreamComponent implements OnInit, OnDestroy {
|
||||
/**
|
||||
|
@ -68,7 +68,7 @@ export class StreamComponent implements OnInit, OnDestroy {
|
|||
/**
|
||||
* @ignore
|
||||
*/
|
||||
hoveringTimeout: ReturnType<typeof setTimeout>;
|
||||
hoveringTimeout: NodeJS.Timeout;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
|
@ -92,27 +92,35 @@ export class StreamComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private _streamContainer: ElementRef;
|
||||
private destroy$ = new Subject<void>();
|
||||
private minimalSub: Subscription;
|
||||
private displayParticipantNameSub: Subscription;
|
||||
private displayAudioDetectionSub: Subscription;
|
||||
private videoControlsSub: Subscription;
|
||||
private readonly HOVER_TIMEOUT = 3000;
|
||||
|
||||
private layoutService: LayoutService;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private serviceConfig: ServiceConfigService,
|
||||
private participantService: ParticipantService,
|
||||
private cdkSrv: CdkOverlayService,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
) {}
|
||||
) {
|
||||
this.layoutService = this.serviceConfig.getLayoutService();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.subscribeToStreamDirectives();
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
this.cdkSrv.setSelector('body');
|
||||
if (this.videoControlsSub) this.videoControlsSub.unsubscribe();
|
||||
if (this.displayAudioDetectionSub) this.displayAudioDetectionSub.unsubscribe();
|
||||
if (this.displayParticipantNameSub) this.displayParticipantNameSub.unsubscribe();
|
||||
if (this.minimalSub) this.minimalSub.unsubscribe();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,29 +186,18 @@ export class StreamComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private subscribeToStreamDirectives() {
|
||||
this.libService.minimal$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: boolean) => {
|
||||
this.minimalSub = this.libService.minimal$.subscribe((value: boolean) => {
|
||||
this.isMinimal = value;
|
||||
});
|
||||
|
||||
this.libService.displayParticipantName$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: boolean) => {
|
||||
this.displayParticipantNameSub = this.libService.displayParticipantName$.subscribe((value: boolean) => {
|
||||
this.showParticipantName = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.displayAudioDetection$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: boolean) => {
|
||||
this.displayAudioDetectionSub = this.libService.displayAudioDetection$.subscribe((value: boolean) => {
|
||||
this.showAudioDetection = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.streamVideoControls$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: boolean) => {
|
||||
this.videoControlsSub = this.libService.streamVideoControls$.subscribe((value: boolean) => {
|
||||
this.showVideoControls = value;
|
||||
// this.cd.markForCheck();
|
||||
});
|
||||
|
|
|
@ -1,31 +1,28 @@
|
|||
<head>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0" />
|
||||
</head>
|
||||
<mat-toolbar id="toolbar" class="toolbar-container">
|
||||
<div id="info-container" class="info-container">
|
||||
<div>
|
||||
<img *ngIf="!isMinimal && showLogo" id="branding-logo" [ovLogo]="brandingLogo" />
|
||||
<img *ngIf="!isMinimal && showLogo" id="branding-logo" src="assets/images/logo.png" ovLogo />
|
||||
<div
|
||||
id="session-info-container"
|
||||
[class.collapsed]="recordingStatus === _recordingStatus.STARTED || broadcastingStatus === _broadcastingStatus.STARTED"
|
||||
>
|
||||
<span id="session-name" *ngIf="!isMinimal && showRoomName">{{ roomName }}</span>
|
||||
<span id="session-name" *ngIf="!isMinimal && room && room.name && showSessionName">{{ room.name }}</span>
|
||||
<div
|
||||
id="activities-tag"
|
||||
*ngIf="recordingStatus === _recordingStatus.STARTED || broadcastingStatus === _broadcastingStatus.STARTED"
|
||||
>
|
||||
@if (recordingStatus === _recordingStatus.STARTED) {
|
||||
<div id="recording-tag" class="recording-tag" (click)="openRecordingActivityPanel()">
|
||||
<div *ngIf="recordingStatus === _recordingStatus.STARTED" id="recording-tag" class="recording-tag">
|
||||
<mat-icon class="blink">radio_button_checked</mat-icon>
|
||||
<span class="blink">REC</span>
|
||||
<span *ngIf="recordingTime"> | {{ recordingTime | date: 'H:mm:ss' }}</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (broadcastingStatus === _broadcastingStatus.STARTED) {
|
||||
<!-- Broadcasting tag -->
|
||||
<div id="broadcasting-tag" class="broadcasting-tag">
|
||||
<div *ngIf="broadcastingStatus === _broadcastingStatus.STARTED" id="broadcasting-tag" class="broadcasting-tag">
|
||||
<mat-icon class="blink">sensors</mat-icon>
|
||||
<span class="blink">LIVE</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -35,7 +32,6 @@
|
|||
<button
|
||||
id="camera-btn"
|
||||
mat-icon-button
|
||||
*ngIf="showCameraButton"
|
||||
(click)="toggleCamera()"
|
||||
[disabled]="isConnectionLost || !hasVideoDevices || cameraMuteChanging"
|
||||
[class.warn-btn]="!isCameraEnabled"
|
||||
|
@ -50,7 +46,6 @@
|
|||
<button
|
||||
id="mic-btn"
|
||||
mat-icon-button
|
||||
*ngIf="showMicrophoneButton"
|
||||
(click)="toggleMicrophone()"
|
||||
[disabled]="isConnectionLost || !hasAudioDevices || microphoneMuteChanging"
|
||||
[class.warn-btn]="!isMicrophoneEnabled"
|
||||
|
@ -126,36 +121,18 @@
|
|||
*ngIf="!isMinimal && showRecordingButton"
|
||||
mat-menu-item
|
||||
id="recording-btn"
|
||||
[disabled]="
|
||||
recordingStatus === _recordingStatus.STARTING ||
|
||||
recordingStatus === _recordingStatus.STOPPING ||
|
||||
!hasRoomTracksPublished
|
||||
"
|
||||
[matTooltip]="!hasRoomTracksPublished ? ('TOOLBAR.NO_TRACKS_PUBLISHED' | translate) : ''"
|
||||
[disabled]="recordingStatus === _recordingStatus.STARTING || recordingStatus === _recordingStatus.STOPPING"
|
||||
(click)="toggleRecording()"
|
||||
>
|
||||
<mat-icon color="warn">radio_button_checked</mat-icon>
|
||||
@if (
|
||||
recordingStatus === _recordingStatus.STOPPED ||
|
||||
recordingStatus === _recordingStatus.STOPPING ||
|
||||
recordingStatus === _recordingStatus.FAILED
|
||||
) {
|
||||
<span class="blink">
|
||||
<span *ngIf="recordingStatus === _recordingStatus.STOPPED || recordingStatus === _recordingStatus.STOPPING">
|
||||
{{ 'TOOLBAR.START_RECORDING' | translate }}
|
||||
</span>
|
||||
} @else if (recordingStatus === _recordingStatus.STARTED || recordingStatus === _recordingStatus.STARTING) {
|
||||
<span>{{ 'TOOLBAR.STOP_RECORDING' | translate }}</span>
|
||||
}
|
||||
<span *ngIf="recordingStatus === _recordingStatus.STARTED || recordingStatus === _recordingStatus.STARTING">
|
||||
{{ 'TOOLBAR.STOP_RECORDING' | translate }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<!-- View recordings button -->
|
||||
@if (!isMinimal && showViewRecordingsButton) {
|
||||
<button mat-menu-item id="view-recordings-btn" (click)="onViewRecordingsClicked.emit()">
|
||||
<mat-icon>video_library</mat-icon>
|
||||
<span>{{ 'TOOLBAR.VIEW_RECORDINGS' | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
|
||||
<!-- Broadcasting button -->
|
||||
<button
|
||||
*ngIf="!isMinimal && showBroadcastingButton"
|
||||
|
|
|
@ -25,18 +25,16 @@ With the following directives you can modify the default User Interface with the
|
|||
<!-- start-dynamic-api-directives-content -->
|
||||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **activitiesPanelButton** | `boolean` | [ToolbarActivitiesPanelButtonDirective](../directives/ToolbarActivitiesPanelButtonDirective.html) |
|
||||
| **backgroundEffectsButton** | `boolean` | [ToolbarBackgroundEffectsButtonDirective](../directives/ToolbarBackgroundEffectsButtonDirective.html) |
|
||||
| **broadcastingButton** | `boolean` | [ToolbarBroadcastingButtonDirective](../directives/ToolbarBroadcastingButtonDirective.html) |
|
||||
| **cameraButton** | `boolean` | [ToolbarCameraButtonDirective](../directives/ToolbarCameraButtonDirective.html) |
|
||||
| **chatPanelButton** | `boolean` | [ToolbarChatPanelButtonDirective](../directives/ToolbarChatPanelButtonDirective.html) |
|
||||
| **displayLogo** | `boolean` | [ToolbarDisplayLogoDirective](../directives/ToolbarDisplayLogoDirective.html) |
|
||||
| **displayRoomName** | `boolean` | [ToolbarDisplayRoomNameDirective](../directives/ToolbarDisplayRoomNameDirective.html) |
|
||||
| **fullscreenButton** | `boolean` | [ToolbarFullscreenButtonDirective](../directives/ToolbarFullscreenButtonDirective.html) |
|
||||
| **leaveButton** | `boolean` | [ToolbarLeaveButtonDirective](../directives/ToolbarLeaveButtonDirective.html) |
|
||||
| **microphoneButton** | `boolean` | [ToolbarMicrophoneButtonDirective](../directives/ToolbarMicrophoneButtonDirective.html) |
|
||||
| **participantsPanelButton** | `boolean` | [ToolbarParticipantsPanelButtonDirective](../directives/ToolbarParticipantsPanelButtonDirective.html) |
|
||||
| **recordingButton** | `boolean` | [ToolbarRecordingButtonDirective](../directives/ToolbarRecordingButtonDirective.html) |
|
||||
| **screenshareButton** | `boolean` | [ToolbarScreenshareButtonDirective](../directives/ToolbarScreenshareButtonDirective.html) |
|
||||
| **recordingButton** | `boolean` | [ToolbarRecordingButtonDirective](../directives/ToolbarRecordingButtonDirective.html) |
|
||||
| **broadcastingButton** | `boolean` | [ToolbarBroadcastingButtonDirective](../directives/ToolbarBroadcastingButtonDirective.html) |
|
||||
| **fullscreenButton** | `boolean` | [ToolbarFullscreenButtonDirective](../directives/ToolbarFullscreenButtonDirective.html) |
|
||||
| **backgroundEffectsButton** | `boolean` | [ToolbarBackgroundEffectsButtonDirective](../directives/ToolbarBackgroundEffectsButtonDirective.html) |
|
||||
| **settingsButton** | `boolean` | [ToolbarSettingsButtonDirective](../directives/ToolbarSettingsButtonDirective.html) |
|
||||
| **leaveButton** | `boolean` | [ToolbarLeaveButtonDirective](../directives/ToolbarLeaveButtonDirective.html) |
|
||||
| **participantsPanelButton** | `boolean` | [ToolbarParticipantsPanelButtonDirective](../directives/ToolbarParticipantsPanelButtonDirective.html) |
|
||||
| **chatPanelButton** | `boolean` | [ToolbarChatPanelButtonDirective](../directives/ToolbarChatPanelButtonDirective.html) |
|
||||
| **activitiesPanelButton** | `boolean` | [ToolbarActivitiesPanelButtonDirective](../directives/ToolbarActivitiesPanelButtonDirective.html) |
|
||||
| **displayRoomName** | `boolean` | [ToolbarDisplayRoomNameDirective](../directives/ToolbarDisplayRoomNameDirective.html) |
|
||||
| **displayLogo** | `boolean` | [ToolbarDisplayLogoDirective](../directives/ToolbarDisplayLogoDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
||||
|
|
|
@ -126,7 +126,6 @@ $ov-recording-blinking-color: #eb5144;
|
|||
text-align: center;
|
||||
line-height: 20px;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.recording-tag {
|
||||
|
@ -157,9 +156,6 @@ $ov-recording-blinking-color: #eb5144;
|
|||
.mat-mdc-icon-button[disabled] {
|
||||
color: #fff;
|
||||
}
|
||||
::ng-deep .mat-badge-content{
|
||||
background-color: var(--ov-warn-color);
|
||||
}
|
||||
.divider {
|
||||
margin: 8px 0px;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
TemplateRef,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { fromEvent, skip, Subject, takeUntil } from 'rxjs';
|
||||
import { fromEvent, skip, Subscription } from 'rxjs';
|
||||
import { ChatService } from '../../services/chat/chat.service';
|
||||
import { DocumentService } from '../../services/document/document.service';
|
||||
import { PanelService } from '../../services/panel/panel.service';
|
||||
|
@ -44,12 +44,12 @@ import { ParticipantService } from '../../services/participant/participant.servi
|
|||
import { PlatformService } from '../../services/platform/platform.service';
|
||||
import { RecordingService } from '../../services/recording/recording.service';
|
||||
import { StorageService } from '../../services/storage/storage.service';
|
||||
import { TemplateManagerService, ToolbarTemplateConfiguration } from '../../services/template/template-manager.service';
|
||||
import { TranslateService } from '../../services/translate/translate.service';
|
||||
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
|
||||
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
|
||||
import { ParticipantModel } from '../../models/participant.model';
|
||||
import { Room, RoomEvent } from 'livekit-client';
|
||||
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
|
||||
import { ServiceConfigService } from '../../services/config/service-config.service';
|
||||
|
||||
/**
|
||||
* The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}.
|
||||
|
@ -59,8 +59,7 @@ import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
|
|||
selector: 'ov-toolbar',
|
||||
templateUrl: './toolbar.component.html',
|
||||
styleUrls: ['./toolbar.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
standalone: false
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
||||
/**
|
||||
|
@ -78,9 +77,10 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
@ContentChild(ToolbarAdditionalButtonsDirective)
|
||||
set externalAdditionalButtons(externalAdditionalButtons: ToolbarAdditionalButtonsDirective) {
|
||||
this._externalAdditionalButtons = externalAdditionalButtons;
|
||||
// This directive will has value only when ADDITIONAL BUTTONS component (tagged with '*ovToolbarAdditionalButtons' directive)
|
||||
// is inside of the TOOLBAR component tagged with '*ovToolbar' directive
|
||||
if (externalAdditionalButtons) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.toolbarAdditionalButtonsTemplate = externalAdditionalButtons.template;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,23 +89,18 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
@ContentChild(ToolbarAdditionalPanelButtonsDirective)
|
||||
set externalAdditionalPanelButtons(externalAdditionalPanelButtons: ToolbarAdditionalPanelButtonsDirective) {
|
||||
this._externalAdditionalPanelButtons = externalAdditionalPanelButtons;
|
||||
// This directive will has value only when ADDITIONAL PANEL BUTTONS component tagged with '*ovToolbarAdditionalPanelButtons' directive
|
||||
// is inside of the TOOLBAR component tagged with '*ovToolbar' directive
|
||||
if (externalAdditionalPanelButtons) {
|
||||
this.updateTemplatesAndMarkForCheck();
|
||||
this.toolbarAdditionalPanelButtonsTemplate = externalAdditionalPanelButtons.template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This event is emitted when the room has been disconnected.
|
||||
* @deprecated Use {@link ToolbarComponent.onParticipantLeft} instead.
|
||||
*/
|
||||
@Output() onRoomDisconnected: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* This event is emitted when the local participant leaves the room.
|
||||
*/
|
||||
@Output() onParticipantLeft: EventEmitter<ParticipantLeftEvent> = new EventEmitter<ParticipantLeftEvent>();
|
||||
|
||||
/**
|
||||
* This event is emitted when the video state changes, providing information about if the video is enabled (true) or disabled (false).
|
||||
*/
|
||||
|
@ -144,12 +139,6 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
@Output() onBroadcastingStopRequested: EventEmitter<BroadcastingStopRequestedEvent> =
|
||||
new EventEmitter<BroadcastingStopRequestedEvent>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* This event is fired when the user clicks on the view recordings button.
|
||||
*/
|
||||
@Output() onViewRecordingsClicked: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -213,14 +202,6 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
* @ignore
|
||||
*/
|
||||
isMinimal: boolean = false;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
showCameraButton: boolean = true;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
showMicrophoneButton: boolean = true;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -245,11 +226,6 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
showRecordingButton: boolean = true;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
showViewRecordingsButton: boolean = false;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -282,20 +258,10 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
* @ignore
|
||||
*/
|
||||
showLogo: boolean = true;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
brandingLogo: string = '';
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
showRoomName: boolean = true;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
roomName: string = '';
|
||||
showSessionName: boolean = true;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
|
@ -327,11 +293,6 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
recordingStatus: RecordingStatus = RecordingStatus.STOPPED;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
isRecordingReadOnlyMode: boolean = false;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -359,25 +320,36 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
*/
|
||||
recordingTime: Date;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Template configuration managed by the service
|
||||
*/
|
||||
templateConfig: ToolbarTemplateConfiguration = {};
|
||||
|
||||
// Store directive references for template setup
|
||||
private _externalAdditionalButtons?: ToolbarAdditionalButtonsDirective;
|
||||
private _externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
|
||||
|
||||
private log: ILogger;
|
||||
private destroy$ = new Subject<void>();
|
||||
private minimalSub: Subscription;
|
||||
private panelTogglingSubscription: Subscription;
|
||||
private chatMessagesSubscription: Subscription;
|
||||
private localParticipantSubscription: Subscription;
|
||||
private screenshareButtonSub: Subscription;
|
||||
private fullscreenButtonSub: Subscription;
|
||||
private backgroundEffectsButtonSub: Subscription;
|
||||
private leaveButtonSub: Subscription;
|
||||
private recordingButtonSub: Subscription;
|
||||
private broadcastingButtonSub: Subscription;
|
||||
private recordingSubscription: Subscription;
|
||||
private broadcastingSubscription: Subscription;
|
||||
private activitiesPanelButtonSub: Subscription;
|
||||
private participantsPanelButtonSub: Subscription;
|
||||
private chatPanelButtonSub: Subscription;
|
||||
private displayLogoSub: Subscription;
|
||||
private displayRoomNameSub: Subscription;
|
||||
private settingsButtonSub: Subscription;
|
||||
private captionsSubs: Subscription;
|
||||
private additionalButtonsPositionSub: Subscription;
|
||||
private fullscreenChangeSubscription: Subscription;
|
||||
private currentWindowHeight = window.innerHeight;
|
||||
private layoutService: LayoutService;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
constructor(
|
||||
private layoutService: LayoutService,
|
||||
private serviceConfig: ServiceConfigService,
|
||||
private documentService: DocumentService,
|
||||
private chatService: ChatService,
|
||||
private panelService: PanelService,
|
||||
|
@ -393,10 +365,10 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
private broadcastingService: BroadcastingService,
|
||||
private translateService: TranslateService,
|
||||
private storageSrv: StorageService,
|
||||
private cdkOverlayService: CdkOverlayService,
|
||||
private templateManagerService: TemplateManagerService
|
||||
private cdkOverlayService: CdkOverlayService
|
||||
) {
|
||||
this.log = this.loggerSrv.get('ToolbarComponent');
|
||||
this.layoutService = this.serviceConfig.getLayoutService();
|
||||
}
|
||||
/**
|
||||
* @ignore
|
||||
|
@ -424,12 +396,10 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
|
||||
async ngOnInit() {
|
||||
this.room = this.openviduService.getRoom();
|
||||
this.evalAndSetRoomName(this.libService.getRoomName());
|
||||
|
||||
this.hasVideoDevices = this.oVDevicesService.hasVideoDeviceAvailable();
|
||||
this.hasAudioDevices = this.oVDevicesService.hasAudioDeviceAvailable();
|
||||
|
||||
this.setupTemplates();
|
||||
this.subscribeToToolbarDirectives();
|
||||
this.subscribeToUserMediaProperties();
|
||||
|
||||
|
@ -447,55 +417,31 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
|
||||
ngOnDestroy(): void {
|
||||
this.panelService.clear();
|
||||
this.destroy$.next();
|
||||
this.destroy$.complete();
|
||||
if (this.panelTogglingSubscription) this.panelTogglingSubscription.unsubscribe();
|
||||
if (this.chatMessagesSubscription) this.chatMessagesSubscription.unsubscribe();
|
||||
if (this.localParticipantSubscription) this.localParticipantSubscription.unsubscribe();
|
||||
if (this.screenshareButtonSub) this.screenshareButtonSub.unsubscribe();
|
||||
if (this.fullscreenButtonSub) this.fullscreenButtonSub.unsubscribe();
|
||||
if (this.backgroundEffectsButtonSub) this.backgroundEffectsButtonSub.unsubscribe();
|
||||
if (this.leaveButtonSub) this.leaveButtonSub.unsubscribe();
|
||||
if (this.recordingButtonSub) this.recordingButtonSub.unsubscribe();
|
||||
if (this.broadcastingButtonSub) this.broadcastingButtonSub.unsubscribe();
|
||||
if (this.participantsPanelButtonSub) this.participantsPanelButtonSub.unsubscribe();
|
||||
if (this.chatPanelButtonSub) this.chatPanelButtonSub.unsubscribe();
|
||||
if (this.displayLogoSub) this.displayLogoSub.unsubscribe();
|
||||
if (this.displayRoomNameSub) this.displayRoomNameSub.unsubscribe();
|
||||
if (this.minimalSub) this.minimalSub.unsubscribe();
|
||||
if (this.activitiesPanelButtonSub) this.activitiesPanelButtonSub.unsubscribe();
|
||||
if (this.recordingSubscription) this.recordingSubscription.unsubscribe();
|
||||
if (this.broadcastingSubscription) this.broadcastingSubscription.unsubscribe();
|
||||
if (this.settingsButtonSub) this.settingsButtonSub.unsubscribe();
|
||||
if (this.captionsSubs) this.captionsSubs.unsubscribe();
|
||||
if (this.fullscreenChangeSubscription) this.fullscreenChangeSubscription.unsubscribe();
|
||||
if (this.additionalButtonsPositionSub) this.additionalButtonsPositionSub.unsubscribe();
|
||||
this.isFullscreenActive = false;
|
||||
this.cdkOverlayService.setSelector('body');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Sets up all templates using the template manager service
|
||||
*/
|
||||
private setupTemplates(): void {
|
||||
this.templateConfig = this.templateManagerService.setupToolbarTemplates(
|
||||
this._externalAdditionalButtons,
|
||||
this._externalAdditionalPanelButtons
|
||||
);
|
||||
|
||||
// Apply templates to component properties for backward compatibility
|
||||
this.applyTemplateConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Applies the template configuration to component properties
|
||||
*/
|
||||
private applyTemplateConfiguration(): void {
|
||||
if (this.templateConfig.toolbarAdditionalButtonsTemplate) {
|
||||
this.toolbarAdditionalButtonsTemplate = this.templateConfig.toolbarAdditionalButtonsTemplate;
|
||||
}
|
||||
if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) {
|
||||
this.toolbarAdditionalPanelButtonsTemplate = this.templateConfig.toolbarAdditionalPanelButtonsTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* Updates templates and triggers change detection
|
||||
*/
|
||||
private updateTemplatesAndMarkForCheck(): void {
|
||||
this.setupTemplates();
|
||||
this.cd.markForCheck();
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
get hasRoomTracksPublished(): boolean {
|
||||
return this.openviduService.hasRoomTracksPublished();
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
|
@ -550,53 +496,17 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
/**
|
||||
* The participant leaves the room voluntarily.
|
||||
* @ignore
|
||||
*/
|
||||
async disconnect() {
|
||||
try {
|
||||
await this.openviduService.disconnectRoom(() => {
|
||||
this.onParticipantLeft.emit({
|
||||
roomName: this.openviduService.getRoomName(),
|
||||
participantName: this.participantService.getLocalParticipant()?.name || '',
|
||||
identity: this.participantService.getLocalParticipant()?.identity || '',
|
||||
reason: ParticipantLeftReason.LEAVE
|
||||
});
|
||||
await this.openviduService.disconnectRoom();
|
||||
this.onRoomDisconnected.emit();
|
||||
}, false);
|
||||
} catch (error) {
|
||||
this.log.e('There was an error disconnecting:', error.code, error.message);
|
||||
this.actionService.openDialog(this.translateService.translate('ERRORS.DISCONNECT'), error?.error || error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
openRecordingActivityPanel() {
|
||||
if (this.showActivitiesPanelButton && !this.isActivitiesOpened) {
|
||||
this.panelService.togglePanel(PanelType.ACTIVITIES, 'recording');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
openBroadcastingActivityPanel() {
|
||||
if (this.showActivitiesPanelButton && !this.isActivitiesOpened) {
|
||||
this.panelService.togglePanel(PanelType.ACTIVITIES, 'broadcasting');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
toggleRecording() {
|
||||
if (this.recordingStatus === RecordingStatus.FAILED) {
|
||||
this.openRecordingActivityPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: RecordingStartRequestedEvent = {
|
||||
roomName: this.openviduService.getRoomName()
|
||||
};
|
||||
|
@ -606,7 +516,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.onRecordingStopRequested.emit(payload);
|
||||
} else if (this.recordingStatus === RecordingStatus.STOPPED) {
|
||||
this.onRecordingStartRequested.emit(payload);
|
||||
this.openRecordingActivityPanel();
|
||||
if (this.showActivitiesPanelButton && !this.isActivitiesOpened) {
|
||||
this.toggleActivitiesPanel('recording');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -623,7 +535,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.onBroadcastingStopRequested.emit(payload);
|
||||
this.broadcastingService.setBroadcastingStopped();
|
||||
} else if (this.broadcastingStatus === BroadcastingStatus.STOPPED) {
|
||||
this.openBroadcastingActivityPanel();
|
||||
if (this.showActivitiesPanelButton && !this.isActivitiesOpened) {
|
||||
this.toggleActivitiesPanel('broadcasting');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -676,11 +590,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.documentService.toggleFullscreen('session-container');
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* @param expandPanel
|
||||
*/
|
||||
toggleActivitiesPanel(expandPanel: string) {
|
||||
private toggleActivitiesPanel(expandPanel: string) {
|
||||
this.panelService.togglePanel(PanelType.ACTIVITIES, expandPanel);
|
||||
}
|
||||
|
||||
|
@ -695,9 +605,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToFullscreenChanged() {
|
||||
fromEvent(document, 'fullscreenchange')
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe(() => {
|
||||
this.fullscreenChangeSubscription = fromEvent(document, 'fullscreenchange').subscribe(() => {
|
||||
const isFullscreen = Boolean(document.fullscreenElement);
|
||||
if (isFullscreen) {
|
||||
this.cdkOverlayService.setSelector('#session-container');
|
||||
|
@ -711,7 +619,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToMenuToggling() {
|
||||
this.panelService.panelStatusObs.pipe(takeUntil(this.destroy$)).subscribe((ev: PanelStatusInfo) => {
|
||||
this.panelTogglingSubscription = this.panelService.panelStatusObs.subscribe((ev: PanelStatusInfo) => {
|
||||
this.isChatOpened = ev.isOpened && ev.panelType === PanelType.CHAT;
|
||||
this.isParticipantsOpened = ev.isOpened && ev.panelType === PanelType.PARTICIPANTS;
|
||||
this.isActivitiesOpened = ev.isOpened && ev.panelType === PanelType.ACTIVITIES;
|
||||
|
@ -723,7 +631,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToChatMessages() {
|
||||
this.chatService.messagesObs.pipe(skip(1), takeUntil(this.destroy$)).subscribe((messages) => {
|
||||
this.chatMessagesSubscription = this.chatService.messagesObs.pipe(skip(1)).subscribe((messages) => {
|
||||
if (!this.panelService.isChatPanelOpened()) {
|
||||
this.unreadMessages++;
|
||||
}
|
||||
|
@ -732,7 +640,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
});
|
||||
}
|
||||
private subscribeToUserMediaProperties() {
|
||||
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe((p: ParticipantModel | undefined) => {
|
||||
this.localParticipantSubscription = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => {
|
||||
if (p) {
|
||||
if (this.isCameraEnabled !== p.isCameraEnabled) {
|
||||
this.onVideoEnabledChanged.emit(p.isCameraEnabled);
|
||||
|
@ -756,13 +664,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToRecordingStatus() {
|
||||
this.libService.recordingActivityReadOnly$.pipe(takeUntil(this.destroy$)).subscribe((readOnly: boolean) => {
|
||||
this.isRecordingReadOnlyMode = readOnly;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.recordingService.recordingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: RecordingStatusInfo) => {
|
||||
const { status, startedAt } = event;
|
||||
this.recordingSubscription = this.recordingService.recordingStatusObs.subscribe((event: RecordingStatusInfo) => {
|
||||
const { status, recordingElapsedTime } = event;
|
||||
this.recordingStatus = status;
|
||||
if (status === RecordingStatus.STARTED) {
|
||||
this.startedRecording = event.recordingList.find((rec) => rec.status === RecordingStatus.STARTED);
|
||||
|
@ -770,15 +673,15 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.startedRecording = undefined;
|
||||
}
|
||||
|
||||
if (startedAt) {
|
||||
this.recordingTime = startedAt;
|
||||
if (recordingElapsedTime) {
|
||||
this.recordingTime = recordingElapsedTime;
|
||||
}
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private subscribeToBroadcastingStatus() {
|
||||
this.broadcastingService.broadcastingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((ev: BroadcastingStatusInfo) => {
|
||||
this.broadcastingSubscription = this.broadcastingService.broadcastingStatusObs.subscribe((ev: BroadcastingStatusInfo) => {
|
||||
if (!!ev) {
|
||||
this.broadcastingStatus = ev.status;
|
||||
this.broadcastingId = ev.broadcastingId;
|
||||
|
@ -788,97 +691,73 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
}
|
||||
|
||||
private subscribeToToolbarDirectives() {
|
||||
this.libService.minimal$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.minimalSub = this.libService.minimal$.subscribe((value: boolean) => {
|
||||
this.isMinimal = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.brandingLogo$.pipe(takeUntil(this.destroy$)).subscribe((value: string) => {
|
||||
this.brandingLogo = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.toolbarViewRecordingsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showViewRecordingsButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showCameraButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.microphoneButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showMicrophoneButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.screenshareButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.screenshareButtonSub = this.libService.screenshareButton$.subscribe((value: boolean) => {
|
||||
this.showScreenshareButton = value && !this.platformService.isMobile();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.fullscreenButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.fullscreenButtonSub = this.libService.fullscreenButton$.subscribe((value: boolean) => {
|
||||
this.showFullscreenButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.leaveButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.leaveButtonSub = this.libService.leaveButton$.subscribe((value: boolean) => {
|
||||
this.showLeaveButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.recordingButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.recordingButtonSub = this.libService.recordingButton$.subscribe((value: boolean) => {
|
||||
this.showRecordingButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.broadcastingButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.broadcastingButtonSub = this.libService.broadcastingButton$.subscribe((value: boolean) => {
|
||||
this.showBroadcastingButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.toolbarSettingsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.settingsButtonSub = this.libService.toolbarSettingsButton$.subscribe((value: boolean) => {
|
||||
this.showSettingsButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.chatPanelButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.chatPanelButtonSub = this.libService.chatPanelButton$.subscribe((value: boolean) => {
|
||||
this.showChatPanelButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.participantsPanelButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.participantsPanelButtonSub = this.libService.participantsPanelButton$.subscribe((value: boolean) => {
|
||||
this.showParticipantsPanelButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.activitiesPanelButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.activitiesPanelButtonSub = this.libService.activitiesPanelButton$.subscribe((value: boolean) => {
|
||||
this.showActivitiesPanelButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.backgroundEffectsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.backgroundEffectsButtonSub = this.libService.backgroundEffectsButton$.subscribe((value: boolean) => {
|
||||
this.showBackgroundEffectsButton = value;
|
||||
this.checkDisplayMoreOptions();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.libService.displayLogo$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.displayLogoSub = this.libService.displayLogo$.subscribe((value: boolean) => {
|
||||
this.showLogo = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.displayRoomName$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.showRoomName = value;
|
||||
this.displayRoomNameSub = this.libService.displayRoomName$.subscribe((value: boolean) => {
|
||||
this.showSessionName = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
this.captionsSubs = this.libService.captionsButton$.subscribe((value: boolean) => {
|
||||
this.showCaptionsButton = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
||||
this.libService.roomName$.pipe(takeUntil(this.destroy$)).subscribe((value: string) => {
|
||||
this.evalAndSetRoomName(value);
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
// this.libService.captionsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
// this.showCaptionsButton = value;
|
||||
// this.cd.markForCheck();
|
||||
// });
|
||||
|
||||
this.libService.toolbarAdditionalButtonsPosition$
|
||||
.pipe(takeUntil(this.destroy$))
|
||||
.subscribe((value: ToolbarAdditionalButtonsPosition) => {
|
||||
this.additionalButtonsPositionSub = this.libService.toolbarAdditionalButtonsPosition$.subscribe(
|
||||
(value: ToolbarAdditionalButtonsPosition) => {
|
||||
// Using Promise.resolve() to defer change detection until the next microtask.
|
||||
// This ensures that Angular's change detection has the latest value before updating the view.
|
||||
// Without this, Angular's OnPush strategy might not immediately reflect the change,
|
||||
|
@ -888,11 +767,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.additionalButtonsPosition = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private subscribeToCaptionsToggling() {
|
||||
this.layoutService.captionsTogglingObs.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
|
||||
this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
|
||||
this.captionsEnabled = value;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
|
@ -906,14 +786,4 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
|
|||
this.showBroadcastingButton ||
|
||||
this.showSettingsButton;
|
||||
}
|
||||
|
||||
private evalAndSetRoomName(value: string) {
|
||||
if (!!value) {
|
||||
this.roomName = value;
|
||||
} else if (!!this.room && this.room.name) {
|
||||
this.roomName = this.room.name;
|
||||
} else {
|
||||
this.roomName = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
<div id="call-container">
|
||||
<div id="spinner" *ngIf="componentState.isLoading">
|
||||
<mat-spinner [diameter]="spinnerDiameter"></mat-spinner>
|
||||
<div id="spinner" *ngIf="loading">
|
||||
<mat-spinner [diameter]="50"></mat-spinner>
|
||||
<span>{{ 'PREJOIN.PREPARING' | translate }}</span>
|
||||
</div>
|
||||
|
||||
<div [@inOutAnimation] id="pre-join-container" *ngIf="componentState.showPrejoin && !componentState.isLoading">
|
||||
<ng-container *ngIf="openviduAngularPreJoinTemplate; else defaultPreJoin">
|
||||
<ng-container *ngTemplateOutlet="openviduAngularPreJoinTemplate"></ng-container>
|
||||
</ng-container>
|
||||
<ng-template #defaultPreJoin>
|
||||
<div [@inOutAnimation] id="pre-join-container" *ngIf="showPrejoin && !loading">
|
||||
<ov-pre-join
|
||||
[error]="componentState.error?.tokenError"
|
||||
[error]="_tokenError"
|
||||
(onReadyToJoin)="_onReadyToJoin()"
|
||||
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
|
||||
(onVideoEnabledChanged)="onVideoEnabledChanged.emit($event)"
|
||||
|
@ -18,28 +14,15 @@
|
|||
(onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)"
|
||||
(onLangChanged)="onLangChanged.emit($event)"
|
||||
></ov-pre-join>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
<div id="spinner" *ngIf="!componentState.isLoading && componentState.error?.hasError">
|
||||
<div id="spinner" *ngIf="!loading && error">
|
||||
<mat-icon class="error-icon">error</mat-icon>
|
||||
<span>{{ componentState.error?.message }}</span>
|
||||
<span>{{ errorMessage }}</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
[@inOutAnimation]
|
||||
id="vc-container"
|
||||
*ngIf="componentState.isRoomReady && !componentState.showPrejoin && !componentState.isLoading && !componentState.error?.hasError"
|
||||
>
|
||||
<ov-session
|
||||
(onRoomCreated)="onRoomCreated.emit($event)"
|
||||
(onRoomReconnecting)="onRoomDisconnected.emit()"
|
||||
(onRoomDisconnected)="onRoomDisconnected.emit()"
|
||||
(onRoomReconnected)="onRoomReconnected.emit()"
|
||||
(onParticipantConnected)="onParticipantCreated.emit($event)"
|
||||
(onParticipantConnected)="onParticipantConnected.emit($event)"
|
||||
(onParticipantLeft)="_onParticipantLeft($event)"
|
||||
>
|
||||
<div [@inOutAnimation] id="vc-container" *ngIf="isRoomReady && !showPrejoin && !loading && !error">
|
||||
<ov-session (onRoomCreated)="onRoomCreated.emit($event)" (onParticipantCreated)="onParticipantCreated.emit($event)">
|
||||
<ng-template #toolbar>
|
||||
<ng-container *ngIf="openviduAngularToolbarTemplate">
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarTemplate"></ng-container>
|
||||
|
@ -64,8 +47,7 @@
|
|||
<ng-template #defaultToolbar>
|
||||
<ov-toolbar
|
||||
id="default-toolbar"
|
||||
(onParticipantLeft)="_onParticipantLeft($event)"
|
||||
(onRoomDisconnected)="onRoomDisconnected.emit()"
|
||||
(onRoomDisconnected)="_onRoomDisconnected()"
|
||||
(onVideoEnabledChanged)="onVideoEnabledChanged.emit($event)"
|
||||
(onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)"
|
||||
(onScreenShareEnabledChanged)="onScreenShareEnabledChanged.emit($event)"
|
||||
|
@ -73,7 +55,6 @@
|
|||
(onRecordingStartRequested)="onRecordingStartRequested.emit($event)"
|
||||
(onRecordingStopRequested)="onRecordingStopRequested.emit($event)"
|
||||
(onBroadcastingStopRequested)="onBroadcastingStopRequested.emit($event)"
|
||||
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
|
||||
>
|
||||
<ng-template #toolbarAdditionalButtons>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarAdditionalButtonsTemplate"></ng-container>
|
||||
|
@ -138,8 +119,6 @@
|
|||
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
|
||||
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
|
||||
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
|
||||
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
|
||||
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
|
||||
(onBroadcastingStartRequested)="onBroadcastingStartRequested.emit($event)"
|
||||
(onBroadcastingStopRequested)="onBroadcastingStopRequested.emit($event)"
|
||||
></ov-activities-panel>
|
||||
|
@ -152,9 +131,6 @@
|
|||
*ngTemplateOutlet="openviduAngularParticipantPanelItemTemplate; context: { $implicit: participant }"
|
||||
></ng-container>
|
||||
</ng-template>
|
||||
<ng-template #participantPanelAfterLocalParticipant>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularParticipantPanelAfterLocalParticipantTemplate"></ng-container>
|
||||
</ng-template>
|
||||
</ov-participants-panel>
|
||||
</ng-template>
|
||||
|
||||
|
@ -173,10 +149,6 @@
|
|||
<ng-template #stream let-track>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularStreamTemplate; context: { $implicit: track }"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #layoutAdditionalElements>
|
||||
<ng-container *ngTemplateOutlet="ovLayoutAdditionalElementsTemplate"></ng-container>
|
||||
</ng-template>
|
||||
</ov-layout>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -23,35 +23,32 @@ With the following directives you can modify the default User Interface with the
|
|||
<!-- start-dynamic-api-directives-content -->
|
||||
| **Parameter** | **Type** | **Reference** |
|
||||
|:--------------------------------: | :-------: | :---------------------------------------------: |
|
||||
| **activitiesPanelBroadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
|
||||
| **activitiesPanelRecordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) |
|
||||
| **audioEnabled** | `boolean` | [AudioEnabledDirective](../directives/AudioEnabledDirective.html) |
|
||||
| **lang** | `AvailableLangs` | [LangDirective](../directives/LangDirective.html) |
|
||||
| **langOptions** | `LangOption[]` | [LangOptionsDirective](../directives/LangOptionsDirective.html) |
|
||||
| **livekitUrl** | `string` | [LivekitUrlDirective](../directives/LivekitUrlDirective.html) |
|
||||
| **minimal** | `boolean` | [MinimalDirective](../directives/MinimalDirective.html) |
|
||||
| **participantName** | `string` | [ParticipantNameDirective](../directives/ParticipantNameDirective.html) |
|
||||
| **participantPanelItemMuteButton** | `boolean` | [ParticipantPanelItemMuteButtonDirective](../directives/ParticipantPanelItemMuteButtonDirective.html) |
|
||||
| **prejoin** | `boolean` | [PrejoinDirective](../directives/PrejoinDirective.html) |
|
||||
| **recordingStreamBaseUrl** | `string` | [RecordingStreamBaseUrlDirective](../directives/RecordingStreamBaseUrlDirective.html) |
|
||||
| **streamDisplayAudioDetection** | `boolean` | [StreamDisplayAudioDetectionDirective](../directives/StreamDisplayAudioDetectionDirective.html) |
|
||||
| **streamDisplayParticipantName** | `boolean` | [StreamDisplayParticipantNameDirective](../directives/StreamDisplayParticipantNameDirective.html) |
|
||||
| **streamVideoControls** | `boolean` | [StreamVideoControlsDirective](../directives/StreamVideoControlsDirective.html) |
|
||||
| **token** | `string` | [TokenDirective](../directives/TokenDirective.html) |
|
||||
| **tokenError** | `any` | [TokenErrorDirective](../directives/TokenErrorDirective.html) |
|
||||
| **toolbarActivitiesPanelButton** | `boolean` | [ToolbarActivitiesPanelButtonDirective](../directives/ToolbarActivitiesPanelButtonDirective.html) |
|
||||
| **toolbarBackgroundEffectsButton** | `boolean` | [ToolbarBackgroundEffectsButtonDirective](../directives/ToolbarBackgroundEffectsButtonDirective.html) |
|
||||
| **toolbarBroadcastingButton** | `boolean` | [ToolbarBroadcastingButtonDirective](../directives/ToolbarBroadcastingButtonDirective.html) |
|
||||
| **toolbarCameraButton** | `boolean` | [ToolbarCameraButtonDirective](../directives/ToolbarCameraButtonDirective.html) |
|
||||
| **toolbarChatPanelButton** | `boolean` | [ToolbarChatPanelButtonDirective](../directives/ToolbarChatPanelButtonDirective.html) |
|
||||
| **toolbarDisplayLogo** | `boolean` | [ToolbarDisplayLogoDirective](../directives/ToolbarDisplayLogoDirective.html) |
|
||||
| **toolbarDisplayRoomName** | `boolean` | [ToolbarDisplayRoomNameDirective](../directives/ToolbarDisplayRoomNameDirective.html) |
|
||||
| **toolbarFullscreenButton** | `boolean` | [ToolbarFullscreenButtonDirective](../directives/ToolbarFullscreenButtonDirective.html) |
|
||||
| **toolbarLeaveButton** | `boolean` | [ToolbarLeaveButtonDirective](../directives/ToolbarLeaveButtonDirective.html) |
|
||||
| **toolbarMicrophoneButton** | `boolean` | [ToolbarMicrophoneButtonDirective](../directives/ToolbarMicrophoneButtonDirective.html) |
|
||||
| **toolbarParticipantsPanelButton** | `boolean` | [ToolbarParticipantsPanelButtonDirective](../directives/ToolbarParticipantsPanelButtonDirective.html) |
|
||||
| **toolbarRecordingButton** | `boolean` | [ToolbarRecordingButtonDirective](../directives/ToolbarRecordingButtonDirective.html) |
|
||||
| **toolbarScreenshareButton** | `boolean` | [ToolbarScreenshareButtonDirective](../directives/ToolbarScreenshareButtonDirective.html) |
|
||||
| **toolbarSettingsButton** | `boolean` | [ToolbarSettingsButtonDirective](../directives/ToolbarSettingsButtonDirective.html) |
|
||||
| **minimal** | `boolean` | [MinimalDirective](../directives/MinimalDirective.html) |
|
||||
| **lang** | `string` | [LangDirective](../directives/LangDirective.html) |
|
||||
| **langOptions** | `LangOption` | [LangOptionsDirective](../directives/LangOptionsDirective.html) |
|
||||
| **participantName** | `string` | [ParticipantNameDirective](../directives/ParticipantNameDirective.html) |
|
||||
| **prejoin** | `boolean` | [PrejoinDirective](../directives/PrejoinDirective.html) |
|
||||
| **videoEnabled** | `boolean` | [VideoEnabledDirective](../directives/VideoEnabledDirective.html) |
|
||||
| **audioEnabled** | `boolean` | [AudioEnabledDirective](../directives/AudioEnabledDirective.html) |
|
||||
| **toolbarScreenshareButton** | `boolean` | [ToolbarScreenshareButtonDirective](../directives/ToolbarScreenshareButtonDirective.html) |
|
||||
| **toolbarRecordingButton** | `boolean` | [ToolbarRecordingButtonDirective](../directives/ToolbarRecordingButtonDirective.html) |
|
||||
| **toolbarBroadcastingButton** | `boolean` | [ToolbarBroadcastingButtonDirective](../directives/ToolbarBroadcastingButtonDirective.html) |
|
||||
| **toolbarFullscreenButton** | `boolean` | [ToolbarFullscreenButtonDirective](../directives/ToolbarFullscreenButtonDirective.html) |
|
||||
| **toolbarBackgroundEffectsButton** | `boolean` | [ToolbarBackgroundEffectsButtonDirective](../directives/ToolbarBackgroundEffectsButtonDirective.html) |
|
||||
| **toolbarSettingsButton** | `boolean` | [ToolbarSettingsButtonDirective](../directives/ToolbarSettingsButtonDirective.html) |
|
||||
| **toolbarLeaveButton** | `boolean` | [ToolbarLeaveButtonDirective](../directives/ToolbarLeaveButtonDirective.html) |
|
||||
| **toolbarParticipantsPanelButton** | `boolean` | [ToolbarParticipantsPanelButtonDirective](../directives/ToolbarParticipantsPanelButtonDirective.html) |
|
||||
| **toolbarChatPanelButton** | `boolean` | [ToolbarChatPanelButtonDirective](../directives/ToolbarChatPanelButtonDirective.html) |
|
||||
| **toolbarActivitiesPanelButton** | `boolean` | [ToolbarActivitiesPanelButtonDirective](../directives/ToolbarActivitiesPanelButtonDirective.html) |
|
||||
| **toolbarDisplayRoomName** | `boolean` | [ToolbarDisplayRoomNameDirective](../directives/ToolbarDisplayRoomNameDirective.html) |
|
||||
| **toolbarDisplayLogo** | `boolean` | [ToolbarDisplayLogoDirective](../directives/ToolbarDisplayLogoDirective.html) |
|
||||
| **streamDisplayParticipantName** | `boolean` | [StreamDisplayParticipantNameDirective](../directives/StreamDisplayParticipantNameDirective.html) |
|
||||
| **streamDisplayAudioDetection** | `boolean` | [StreamDisplayAudioDetectionDirective](../directives/StreamDisplayAudioDetectionDirective.html) |
|
||||
| **streamVideoControls** | `boolean` | [StreamVideoControlsDirective](../directives/StreamVideoControlsDirective.html) |
|
||||
| **participantPanelItemMuteButton** | `boolean` | [ParticipantPanelItemMuteButtonDirective](../directives/ParticipantPanelItemMuteButtonDirective.html) |
|
||||
| **activitiesPanelRecordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) |
|
||||
| **activitiesPanelBroadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
|
||||
<!-- end-dynamic-api-directives-content -->
|
File diff suppressed because it is too large
Load Diff
|
@ -3,6 +3,7 @@ import { ParticipantProperties } from '../models/participant.model';
|
|||
export interface OpenViduComponentsConfig {
|
||||
production?: boolean;
|
||||
participantFactory?: ParticipantFactoryFunction;
|
||||
services?: any;
|
||||
}
|
||||
|
||||
export type ParticipantFactoryFunction = (props: ParticipantProperties) => any;
|
||||
|
|
|
@ -17,8 +17,7 @@ import { OpenViduComponentsConfigService } from '../../services/config/directive
|
|||
* <ov-activities-panel *ovActivitiesPanel [recordingActivity]="false"></ov-activities-panel>
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[activitiesPanelRecordingActivity], ov-activities-panel[recordingActivity]',
|
||||
standalone: false
|
||||
selector: 'ov-videoconference[activitiesPanelRecordingActivity], ov-activities-panel[recordingActivity]'
|
||||
})
|
||||
export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set activitiesPanelRecordingActivity(value: boolean) {
|
||||
|
@ -32,10 +31,7 @@ export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit,
|
|||
|
||||
recordingActivityValue: boolean = true;
|
||||
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
) {}
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.recordingActivityValue);
|
||||
|
@ -49,7 +45,9 @@ export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit,
|
|||
}
|
||||
|
||||
update(value: boolean) {
|
||||
this.libService.updateRecordingActivityConfig({ enabled: value });
|
||||
if (this.libService.showRecordingActivity() !== value) {
|
||||
this.libService.setRecordingActivity(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,9 +66,8 @@ export class ActivitiesPanelRecordingActivityDirective implements AfterViewInit,
|
|||
* @example
|
||||
* <ov-activities-panel *ovActivitiesPanel [broadcastingActivity]="false"></ov-activities-panel>
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[activitiesPanelBroadcastingActivity], ov-activities-panel[broadcastingActivity]',
|
||||
standalone: false
|
||||
@Directive({
|
||||
selector: 'ov-videoconference[activitiesPanelBroadcastingActivity], ov-activities-panel[broadcastingActivity]'
|
||||
})
|
||||
export class ActivitiesPanelBroadcastingActivityDirective implements AfterViewInit, OnDestroy {
|
||||
@Input() set activitiesPanelBroadcastingActivity(value: boolean) {
|
||||
|
@ -84,10 +81,7 @@ export class ActivitiesPanelBroadcastingActivityDirective implements AfterViewIn
|
|||
|
||||
broadcastingActivityValue: boolean = true;
|
||||
|
||||
constructor(
|
||||
public elementRef: ElementRef,
|
||||
private libService: OpenViduComponentsConfigService
|
||||
) {}
|
||||
constructor(public elementRef: ElementRef, private libService: OpenViduComponentsConfigService) {}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.update(this.broadcastingActivityValue);
|
||||
|
@ -101,6 +95,9 @@ export class ActivitiesPanelBroadcastingActivityDirective implements AfterViewIn
|
|||
}
|
||||
|
||||
update(value: boolean) {
|
||||
if (this.libService.showBroadcastingActivity() !== value) {
|
||||
this.libService.setBroadcastingActivity(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue