Compare commits

..

No commits in common. "master" and "v3.0.0-beta3" have entirely different histories.

266 changed files with 21269 additions and 57053 deletions

113
.github/workflows/openvidu-ce-test.yml vendored Normal file
View File

@ -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

View File

@ -45,7 +45,7 @@ jobs:
https://api.github.com/repos/OpenVidu/openvidu-call/dispatches \ 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"'"}}' -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 needs: test_setup
name: Nested events name: Nested events
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -58,33 +58,74 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome # - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable # run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome - 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 - name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main run: |
- name: Start OpenVidu Call backend git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-call@main 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 - name: Install dependencies
run: | run: |
cd openvidu-components-angular cd openvidu-components-angular
npm install npm install
- name: Build and Serve openvidu-components-angular Testapp - name: Build openvidu-components-angular
uses: OpenVidu/actions/start-openvidu-components-testapp@main 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 - name: Run nested components E2E event tests
env: env:
LAUNCH_MODE: CI LAUNCH_MODE: CI
run: npm run e2e:nested-events --prefix openvidu-components-angular 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 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 runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
@ -95,29 +136,53 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome # - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable # run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome - 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 - name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main run: |
- name: Start OpenVidu Call backend git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-call@main cd openvidu-local-deployment/community
- name: Build and Serve openvidu-components-angular Testapp ./configure_lan_private_ip_linux.sh
uses: OpenVidu/actions/start-openvidu-components-testapp@main docker compose up -d
- name: Run nested structural directives tests - 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: env:
LAUNCH_MODE: CI LAUNCH_MODE: CI
run: npm run e2e:nested-structural-directives --prefix openvidu-components-angular run: npm run e2e:nested-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
nested_attribute_directives: webcomponent_e2e_directives:
needs: test_setup needs: test_setup
name: Nested Attribute Directives name: Webcomponent directives
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout Repository - name: Checkout Repository
@ -128,318 +193,439 @@ jobs:
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: '20' node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome # - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable # run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome - 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 - name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main run: |
- name: Start OpenVidu Call backend git clone --depth 1 https://github.com/OpenVidu/openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-call@main cd openvidu-local-deployment/community
- name: Build and Serve openvidu-components-angular Testapp ./configure_lan_private_ip_linux.sh
uses: OpenVidu/actions/start-openvidu-components-testapp@main docker compose up -d
- name: Run nested attribute directives tests - name: Run OpenVidu Call Backend
env: run: |
LAUNCH_MODE: CI git clone --depth 1 https://github.com/OpenVidu/openvidu-call
run: npm run e2e:nested-attribute-directives --prefix openvidu-components-angular cd openvidu-call/backend
- name: Cleanup npm install
if: always() npm run dev:start &
uses: OpenVidu/actions/cleanup@main - name: Install dependencies
run: |
e2e_directives: cd openvidu-components-angular
needs: test_setup npm install
name: API Directives Tests - name: Build openvidu-angular
runs-on: ubuntu-latest run: npm run lib:build --prefix openvidu-components-angular
steps: - name: Build openvidu-webcomponent
- name: Checkout Repository run: npm run webcomponent:testing-build --prefix openvidu-components-angular
uses: actions/checkout@v4 - name: Serve Webcomponent Testapp
with: run: npm run webcomponent:serve-testapp --prefix openvidu-components-angular &
ref: ${{ inputs.commit_sha || github.sha }} - name: Wait for openvidu-local-deployment
- name: Setup Node.js run: |
uses: actions/setup-node@v4 until curl -s -f -o /dev/null http://localhost:7880; do
with: echo "Waiting for openvidu-local-deployment to be ready..."
node-version: '20' sleep 5
- name: Install wait-on package done
run: npm install -g wait-on - name: Wait for openvidu-components-angular Testapp
# - name: Run Browserless Chrome run: |
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable until curl -s -f -o /dev/null http://localhost:8080; do
- name: Run Chrome echo "Waiting for openvidu-components-angular Testapp to be ready..."
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0 sleep 5
- name: Run openvidu-local-deployment done
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_internal_directives:
needs: test_setup
name: Internal Directives Tests
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-internal-directives --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_chat:
needs: test_setup
name: Chat E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-chat --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_events:
needs: test_setup
name: Events E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-events --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_media_devices:
needs: test_setup
name: Media devices E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-media-devices --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_panels:
needs: test_setup
name: Panels E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-panels --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_screen_sharing:
needs: test_setup
name: Screen sharing E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-screensharing --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_stream:
needs: test_setup
name: Stream E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -v $(pwd)/openvidu-components-angular/e2e/assets:/e2e-assets selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Tests
env:
LAUNCH_MODE: CI
run: npm run e2e:lib-stream --prefix openvidu-components-angular
- name: Cleanup
if: always()
uses: OpenVidu/actions/cleanup@main
e2e_toolbar:
needs: test_setup
name: Toolbar E2E
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.commit_sha || github.sha }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install wait-on package
run: npm install -g wait-on
# - name: Run Browserless Chrome
# run: docker run -d -p 3000:3000 --network host browserless/chrome:1.57-chrome-stable
- name: Run Chrome
run: docker run --network=host -d -p 4444:4444 selenium/standalone-chrome:138.0
- name: Run openvidu-local-deployment
uses: OpenVidu/actions/start-openvidu-local-deployment@main
- name: Start OpenVidu Call backend
uses: OpenVidu/actions/start-openvidu-call@main
- name: Build and Serve openvidu-components-angular Testapp
uses: OpenVidu/actions/start-openvidu-components-testapp@main
- name: Run Webcomponent E2E - name: Run Webcomponent E2E
env: env:
LAUNCH_MODE: CI LAUNCH_MODE: CI
run: npm run e2e:lib-toolbar --prefix openvidu-components-angular run: npm run e2e:webcomponent-directives --prefix openvidu-components-angular
- name: Cleanup
if: always() webcomponent_e2e_chat:
uses: OpenVidu/actions/cleanup@main 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

View File

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

View File

@ -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. 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 ## Sponsors

View File

@ -10,4 +10,9 @@
node_modules node_modules
dist/ dist/
docs/ docs/
openvidu-webcomponent/
coverage/** coverage/**
e2e/webcomponent-app/openvidu-webcomponent-*.css
e2e/webcomponent-app/openvidu-webcomponent-*.js
e2e/assets/*

View File

@ -18,8 +18,7 @@
"builder": "@angular-devkit/build-angular:application", "builder": "@angular-devkit/build-angular:application",
"options": { "options": {
"outputPath": { "outputPath": {
"base": "dist/openvidu-components-testapp", "base": "dist/openvidu-components-testapp"
"browser": ""
}, },
"index": "src/index.html", "index": "src/index.html",
"polyfills": ["zone.js"], "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": { "cli": {

View File

@ -1,3 +1,4 @@
export const LAUNCH_MODE = process.env.LAUNCH_MODE || 'DEV'; 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; jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000;

View File

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

View File

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

View File

@ -5,7 +5,7 @@ import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl; const url = NestedConfig.appUrl;
describe('E2E: Toolbar structural directive scenarios', () => { describe('Testing TOOLBAR STRUCTURAL DIRECTIVES', () => {
let browser: WebDriver; let browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
@ -24,13 +24,10 @@ describe('E2E: Toolbar structural directive scenarios', () => {
afterEach(async () => { afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot()); // console.log('data:image/png;base64,' + await browser.takeScreenshot());
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox'); await utils.clickOn('#ovToolbar-checkbox');
@ -48,7 +45,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
expect(await utils.isPresent('#default-toolbar')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox'); await utils.clickOn('#ovToolbar-checkbox');
@ -72,7 +69,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
expect(await utils.isPresent('#default-toolbar')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox'); await utils.clickOn('#ovToolbar-checkbox');
@ -96,7 +93,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
expect(await utils.isPresent('#default-toolbar')).toBeFalse(); 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; let element;
await browser.get(`${url}`); await browser.get(`${url}`);
@ -119,7 +116,7 @@ describe('E2E: Toolbar structural directive scenarios', () => {
expect(await utils.isPresent('#custom-toolbar')).toBeFalse(); 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; let element;
await browser.get(`${url}`); await browser.get(`${url}`);
@ -136,14 +133,14 @@ describe('E2E: Toolbar structural directive scenarios', () => {
expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue(); expect(await utils.isPresent('#custom-toolbar-additional-panel-buttons')).toBeTrue();
element = await browser.findElements(By.id('toolbar-additional-panel-btn')); 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 // Check if custom toolbar not is present
expect(await utils.isPresent('#custom-toolbar')).toBeFalse(); expect(await utils.isPresent('#custom-toolbar')).toBeFalse();
}); });
}); });
describe('E2E: Panel structural directive scenarios', () => { describe('Testing PANEL STRUCTURAL DIRECTIVES', () => {
let browser: WebDriver; let browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
@ -162,49 +159,10 @@ describe('E2E: Panel structural directive scenarios', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });
it('should render an additional custom panel with default panels', async () => { it('should inject the CUSTOM PANEL without children', async () => {
let element;
await browser.get(`${url}`);
await utils.clickOn('#ovAdditionalPanels-checkbox');
await utils.clickOn('#apply-btn');
// Check if toolbar panel buttons are present
await utils.checkToolbarIsPresent();
// Open additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
await browser.sleep(500);
// Check if custom panel is present
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
element = await utils.waitForElement('#additional-panel-title');
expect(await element.getAttribute('innerText')).toEqual('NEW PANEL');
// Open the participants panel
await utils.clickOn('#participants-panel-btn');
await browser.sleep(500);
// Check if default panel is present
expect(await utils.isPresent('#default-participants-panel')).toBeTrue();
// Open additional panel again
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeTrue();
// Close the additional panel
await utils.clickOn('#toolbar-additional-panel-btn');
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse();
});
it('should render only the custom panel container (no children, no default panels)', async () => {
await browser.get(`${url}`); await browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); await utils.clickOn('#ovPanel-checkbox');
@ -240,7 +198,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse(); 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; let element;
await browser.get(`${url}`); await browser.get(`${url}`);
@ -269,7 +227,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); await utils.clickOn('#ovPanel-checkbox');
@ -308,7 +266,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue(); 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; let element;
await browser.get(`${url}`); await browser.get(`${url}`);
@ -335,7 +293,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await element.getAttribute('innerText')).toEqual('CUSTOM ACTIVITIES PANEL'); 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 browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); await utils.clickOn('#ovPanel-checkbox');
@ -373,7 +331,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); await utils.clickOn('#ovPanel-checkbox');
@ -412,7 +370,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); await utils.clickOn('#ovPanel-checkbox');
@ -457,7 +415,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#default-participant-panel-item')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovActivitiesPanel-checkbox'); await utils.clickOn('#ovActivitiesPanel-checkbox');
@ -482,7 +440,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#activities-container')).toBeTrue(); 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; let element;
await browser.get(`${url}`); await browser.get(`${url}`);
@ -508,7 +466,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-additional-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovChatPanel-checkbox'); await utils.clickOn('#ovChatPanel-checkbox');
@ -546,7 +504,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeTrue(); 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 browser.get(`${url}`);
await utils.clickOn('#ovParticipantsPanel-checkbox'); await utils.clickOn('#ovParticipantsPanel-checkbox');
@ -584,7 +542,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItem-checkbox'); await utils.clickOn('#ovParticipantPanelItem-checkbox');
@ -625,7 +583,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovParticipantPanelItemElements-checkbox'); await utils.clickOn('#ovParticipantPanelItemElements-checkbox');
@ -663,7 +621,7 @@ describe('E2E: Panel structural directive scenarios', () => {
expect(await utils.isPresent('#custom-chat-panel')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovPanel-checkbox'); 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
@ -723,13 +681,10 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox'); await utils.clickOn('#ovLayout-checkbox');
@ -750,7 +705,7 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
expect(await utils.isPresent('video')).toBeFalse(); 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 browser.get(`${url}`);
await utils.clickOn('#ovLayout-checkbox'); await utils.clickOn('#ovLayout-checkbox');
@ -778,7 +733,7 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
expect(await utils.isPresent('video')).toBeTrue(); 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 browser.get(`${url}`);
await utils.clickOn('#ovStream-checkbox'); await utils.clickOn('#ovStream-checkbox');
@ -804,3 +759,293 @@ describe('E2E: Layout and stream structural directive scenarios', () => {
expect(await utils.isPresent('video')).toBeTrue(); 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();
});
});

View File

@ -5,7 +5,7 @@ import { OpenViduComponentsPO } from '../utils.po.test';
const url = NestedConfig.appUrl; const url = NestedConfig.appUrl;
describe('OpenVidu Components EVENTS', () => { describe('Testing EVENTS', () => {
let browser: WebDriver; let browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
@ -24,13 +24,10 @@ describe('OpenVidu Components EVENTS', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });
it('should receive the onParticipantLeft event', async () => { it('should receive the onRoomDisconnected event', async () => {
await browser.get(`${url}`); await browser.get(`${url}`);
await utils.clickOn('#ovToolbar-checkbox'); await utils.clickOn('#ovToolbar-checkbox');
@ -43,8 +40,8 @@ describe('OpenVidu Components EVENTS', () => {
await utils.clickOn('#leave-btn'); await utils.clickOn('#leave-btn');
// Checking if onLeaveButtonClicked has been received // Checking if onLeaveButtonClicked has been received
await utils.waitForElement('#onParticipantLeft'); await utils.waitForElement('#onRoomDisconnected');
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue(); expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
}); });
it('should receive the onVideoEnabledChanged event', async () => { it('should receive the onVideoEnabledChanged event', async () => {

View File

@ -10,14 +10,12 @@ interface BrowserConfig {
browserName: string; browserName: string;
} }
const audioPath = LAUNCH_MODE === 'CI' ? `e2e-assets/audio_test.wav` : 'e2e/assets/audio_test.wav';
const chromeArguments = [ const chromeArguments = [
'--window-size=1300,1000', '--window-size=1300,1000',
// '--headless', '--headless',
'--use-fake-ui-for-media-stream', '--use-fake-ui-for-media-stream',
'--use-fake-device-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 = [ const chromeArgumentsCI = [
'--window-size=1300,1000', '--window-size=1300,1000',
@ -31,10 +29,7 @@ const chromeArgumentsCI = [
'--disable-background-networking', '--disable-background-networking',
'--disable-default-apps', '--disable-default-apps',
'--use-fake-ui-for-media-stream', '--use-fake-ui-for-media-stream',
'--use-fake-device-for-media-stream', '--use-fake-device-for-media-stream'
`--use-file-for-fake-audio-capture=${audioPath}`,
'--autoplay-policy=no-user-gesture-required',
'--allow-file-access-from-files'
]; ];
const chromeArgumentsWithoutMediaDevices = ['--headless', '--window-size=1300,900', '--deny-permission-prompts']; const chromeArgumentsWithoutMediaDevices = ['--headless', '--window-size=1300,900', '--deny-permission-prompts'];
const chromeArgumentsWithoutMediaDevicesCI = [ const chromeArgumentsWithoutMediaDevicesCI = [
@ -51,8 +46,8 @@ const chromeArgumentsWithoutMediaDevicesCI = [
'--deny-permission-prompts' '--deny-permission-prompts'
]; ];
export const TestAppConfig: BrowserConfig = { export const WebComponentConfig: BrowserConfig = {
appUrl: 'http://localhost:4200/#/call?staticVideos=false', appUrl: 'http://localhost:8080/',
seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '', seleniumAddress: LAUNCH_MODE === 'CI' ? 'http://localhost:4444/wd/hub' : '',
browserName: 'chrome', browserName: 'chrome',
browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true), browserCapabilities: Capabilities.chrome().set('acceptInsecureCerts', true),

View File

@ -136,42 +136,6 @@ export class OpenViduComponentsPO {
await this.clickOn('#fullscreen-btn'); 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) { async togglePanel(panelName: string) {
switch (panelName) { switch (panelName) {
case 'activities': case 'activities':
@ -195,7 +159,5 @@ export class OpenViduComponentsPO {
await this.clickOn('#toolbar-settings-btn'); await this.clickOn('#toolbar-settings-btn');
break; break;
} }
await this.browser.sleep(500);
} }
} }

View File

@ -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;
}
}

View File

@ -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-primary-color: #303030;
--ov-secondary-color: #3e3f3f;
--ov-tertiary-color: #598eff;
--ov-warn-color: #eb5144;
--ov-accent-color: #ffae35;
--ov-light-color: #e6e6e6;
--ov-logo-background-color: #3a3d3d;
--ov-text-color: #ffffff;
--ov-panel-text-color: #1d1d1d;
--ov-panel-background: #ffffff;
--ov-buttons-radius: 50%;
--ov-leave-button-radius: 10px;
--ov-video-radius: 5px;
--ov-panel-radius: 5px;
}
</style>
</head>
<body>
<div id="events"></div>
<!-- OpenVidu Web Component -->
<openvidu-webcomponent></openvidu-webcomponent>
</body>
</html>

View File

@ -1,21 +1,21 @@
// Ideally we'd use an editor or import shaders directly from the API. // Ideally we'd use an editor or import shaders directly from the API.
import { FilterStream } from './filter-stream.js'; import { FilterStream } from './filter-stream.js';
export const monkeyPatchMediaDevices = () => { export default function monkeyPatchMediaDevices() {
const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices; const enumerateDevicesFn = MediaDevices.prototype.enumerateDevices;
const getUserMediaFn = MediaDevices.prototype.getUserMedia; const getUserMediaFn = MediaDevices.prototype.getUserMedia;
const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia; const getDisplayMediaFn = MediaDevices.prototype.getDisplayMedia;
const fakeVideoDevice = { const fakeVideoDevice = {
deviceId: 'virtual_video', deviceId: 'virtual',
groupId: '', groupID: '',
kind: 'videoinput', kind: 'videoinput',
label: 'custom_fake_video_1' label: 'custom_fake_video_1'
}; };
const fakeAudioDevice = { const fakeAudioDevice = {
deviceId: 'virtual_audio', deviceId: 'virtual',
groupId: '', groupID: '',
kind: 'audioinput', kind: 'audioinput',
label: 'custom_fake_audio_1' label: 'custom_fake_audio_1'
}; };
@ -29,21 +29,8 @@ export const monkeyPatchMediaDevices = () => {
const getUserMediaMonkeyPatch = async function () { const getUserMediaMonkeyPatch = async function () {
const args = arguments[0]; 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; const { deviceId, advanced, width, height } = args.video;
if (deviceId === 'virtual' || deviceId?.exact === 'virtual') {
const constraints = { const constraints = {
video: { video: {
facingMode: args.facingMode, facingMode: args.facingMode,

View File

@ -1,35 +1,29 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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', () => { describe('Testing API Directives', () => {
let browser: WebDriver; let browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
beforeEach(async () => { beforeEach(async () => {
browser = await createChromeBrowser(); browser = await createChromeBrowser();
utils = new OpenViduComponentsPO(browser); utils = new OpenViduComponentsPO(browser);
url = `${TestAppConfig.appUrl}&roomName=API_DIRECTIVES_${Math.floor(Math.random() * 1000)}`;
}); });
afterEach(async () => { afterEach(async () => {
// console.log('data:image/png;base64,' + await browser.takeScreenshot()); // 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(); await browser.quit();
}); });
@ -174,7 +168,7 @@ describe('Testing API Directives', () => {
}); });
it('should show the token error WITH prejoin page', async () => { 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}`); await browser.get(`${fixedUrl}`);
// Checking if prejoin page exist // Checking if prejoin page exist
@ -204,7 +198,7 @@ describe('Testing API Directives', () => {
}); });
it('should show the token error WITHOUT prejoin page', async () => { 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}`); await browser.get(`${fixedUrl}`);
// Checking if session container is present // Checking if session container is present
@ -238,11 +232,11 @@ describe('Testing API Directives', () => {
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
await utils.waitForElement('#videocam_off');
expect(await utils.isPresent('#videocam_off')).toBeTrue();
await utils.waitForElement('#video-poster'); await utils.waitForElement('#video-poster');
expect(await utils.getNumberOfElements('video')).toEqual(0); 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 () => { 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 () => { it('should run the app with AUDIO DISABLED and WITHOUT PREJOIN page', async () => {
await browser.get(`${url}&prejoin=false&audioEnabled=false`); await browser.get(`${url}&prejoin=false&audioEnabled=false`);
await browser.sleep(1000);
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
// Checking if video is displayed // Checking if video is displayed
@ -299,30 +292,6 @@ describe('Testing API Directives', () => {
expect(await utils.isPresent('#mic_off')).toBeTrue(); expect(await utils.isPresent('#mic_off')).toBeTrue();
}); });
it('should run the app without camera button', async () => {
await browser.get(`${url}&prejoin=false&cameraBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if camera button is not present
expect(await utils.isPresent('#camera-btn')).toBeFalse();
});
it('should run the app without microphone button', async () => {
await browser.get(`${url}&prejoin=false&microphoneBtn=false`);
await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Checking if microphone button is not present
expect(await utils.isPresent('#microphone-btn')).toBeFalse();
});
it('should HIDE the SCREENSHARE button', async () => { it('should HIDE the SCREENSHARE button', async () => {
await browser.get(`${url}&prejoin=false&screenshareBtn=false`); 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 () => { it('should HIDE the MUTE button in participants panel', async () => {
const roomName = 'e2etest'; 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 browser.get(fixedUrl);
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
@ -558,9 +527,8 @@ describe('Testing API Directives', () => {
expect(await utils.isPresent('#remote-participant-item')).toBeFalse(); expect(await utils.isPresent('#remote-participant-item')).toBeFalse();
// Starting new browser for adding a new participant // 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.executeScript(newTabScript);
await browser.sleep(10000);
// Go to first tab // Go to first tab
const tabs = await browser.getAllWindowHandles(); const tabs = await browser.getAllWindowHandles();

View File

@ -1,8 +1,9 @@
import { Builder, Key, WebDriver } from 'selenium-webdriver'; import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 //TODO: Uncomment when captions are implemented
// describe('Testing captions features', () => { // describe('Testing captions features', () => {
@ -10,10 +11,10 @@ const url = TestAppConfig.appUrl;
// let utils: OpenViduComponentsPO; // let utils: OpenViduComponentsPO;
// async function createChromeBrowser(): Promise<WebDriver> { // async function createChromeBrowser(): Promise<WebDriver> {
// return await new Builder() // return await new Builder()
// .forBrowser(TestAppConfig.browserName) // .forBrowser(WebComponentConfig.browserName)
// .withCapabilities(TestAppConfig.browserCapabilities) // .withCapabilities(WebComponentConfig.browserCapabilities)
// .setChromeOptions(TestAppConfig.browserOptions) // .setChromeOptions(WebComponentConfig.browserOptions)
// .usingServer(TestAppConfig.seleniumAddress) // .usingServer(WebComponentConfig.seleniumAddress)
// .build(); // .build();
// } // }
@ -176,4 +177,4 @@ const url = TestAppConfig.appUrl;
// expect(await button.getText()).toEqual('settingsEspañol'); // expect(await button.getText()).toEqual('settingsEspañol');
// }); // });
// }); // });

View File

@ -1,8 +1,9 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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', () => { describe('Testing CHAT features', () => {
let browser: WebDriver; let browser: WebDriver;
@ -10,10 +11,10 @@ describe('Testing CHAT features', () => {
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -23,10 +24,6 @@ describe('Testing CHAT features', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
// leaving room if connected
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });

View File

@ -1,19 +1,20 @@
import { Builder, Key, WebDriver } from 'selenium-webdriver'; import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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', () => { describe('Testing videoconference EVENTS', () => {
let browser: WebDriver; let browser: WebDriver;
let utils: OpenViduComponentsPO; 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> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -23,10 +24,6 @@ describe('Testing videoconference EVENTS', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
// leaving room if connected
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });
@ -60,6 +57,23 @@ describe('Testing videoconference EVENTS', () => {
expect(await utils.isPresent('#onTokenRequested')).toBeTrue(); 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 () => { it('should receive the onVideoEnabledChanged event when clicking on the prejoin', async () => {
await browser.get(url); await browser.get(url);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -599,7 +613,7 @@ describe('Testing videoconference EVENTS', () => {
expect(await utils.isPresent('#onReadyToJoin')).toBeFalse(); expect(await utils.isPresent('#onReadyToJoin')).toBeFalse();
}); });
// PARTICIPANT EVENTS // * PUBLISHER EVENTS
it('should receive onParticipantCreated event from LOCAL participant', async () => { it('should receive onParticipantCreated event from LOCAL participant', async () => {
const participantName = 'TEST_USER'; const participantName = 'TEST_USER';
@ -608,39 +622,22 @@ describe('Testing videoconference EVENTS', () => {
expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).toBeTrue(); expect(await utils.isPresent(`#${participantName}-onParticipantCreated`)).toBeTrue();
}); });
it('should receive the onParticipantLeft event', async () => { // * ROOM EVENTS
await browser.get(`${url}&prejoin=false&redirectToHome=false`);
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.checkSessionIsPresent();
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
// Clicking to leave button // Checking if leave button is not present
const leaveButton = await utils.waitForElement('#leave-btn'); element = await utils.waitForElement('#leave-btn');
expect(await utils.isPresent('#leave-btn')).toBeTrue(); await element.click();
await leaveButton.click();
await utils.waitForElement('#events'); await utils.waitForElement(`#roomDisconnected`);
// Checking if onParticipantLeft has been received expect(await utils.isPresent(`#roomDisconnected`)).toBeTrue();
await utils.waitForElement('#onParticipantLeft');
expect(await utils.isPresent('#onParticipantLeft')).toBeTrue();
}); });
// * ROOM EVENTS
//TODO: Implement a mechanism to emulate network disconnection
// it('should receive the onRoomDisconnected event', async () => {
// await browser.get(`${url}&prejoin=false`);
// await utils.checkSessionIsPresent();
// await utils.checkToolbarIsPresent();
// // Emulate network disconnection
// await utils.forceCloseWebsocket();
// // Checking if onRoomDisconnected has been received
// await utils.waitForElement('#onRoomDisconnected');
// expect(await utils.isPresent('#onRoomDisconnected')).toBeTrue();
// });
}); });

View File

@ -1,18 +1,19 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, WebDriver } from 'selenium-webdriver';
import { getBrowserOptionsWithoutDevices, TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -22,99 +23,118 @@ describe('Media Devices: Virtual Device Replacement and Permissions Handling', (
}); });
afterEach(async () => { afterEach(async () => {
try { // console.log('data:image/png;base64,' + await browser.takeScreenshot());
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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;'; const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&fakeDevices=true`); await browser.get(`${url}&fakeDevices=true`);
let videoDevices = await utils.waitForElement('#video-devices-form'); let videoDevices = await utils.waitForElement('#video-devices-form');
await videoDevices.click(); await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1'); let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click(); await element.click();
let videoLabel; let videoLabel;
await browser.sleep(1000); await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script); videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1'); expect(videoLabel).toEqual('custom_fake_video_1');
await videoDevices.click(); await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0'); element = await utils.waitForElement('#option-fake_device_0');
await element.click(); await element.click();
await browser.sleep(1000); await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script); videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0'); 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;'; const script = 'return document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0].label;';
await browser.get(`${url}&prejoin=false&fakeDevices=true`); await browser.get(`${url}&prejoin=false&fakeDevices=true`);
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
await utils.togglePanel('settings'); await utils.togglePanel('settings');
await utils.waitForElement('.settings-container'); await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue(); expect(await utils.isPresent('.settings-container')).toBeTrue();
await browser.sleep(500);
await utils.clickOn('#video-opt'); await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue(); expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
let videoDevices = await utils.waitForElement('#video-devices-form'); let videoDevices = await utils.waitForElement('#video-devices-form');
await videoDevices.click(); await videoDevices.click();
let element = await utils.waitForElement('#option-custom_fake_video_1'); let element = await utils.waitForElement('#option-custom_fake_video_1');
await element.click(); await element.click();
let videoLabel; let videoLabel;
await browser.sleep(1000); await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script); videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('custom_fake_video_1'); expect(videoLabel).toEqual('custom_fake_video_1');
await videoDevices.click(); await videoDevices.click();
element = await utils.waitForElement('#option-fake_device_0'); element = await utils.waitForElement('#option-fake_device_0');
await element.click(); await element.click();
await browser.sleep(1000); await browser.sleep(1000);
videoLabel = await browser.executeScript<string>(script); videoLabel = await browser.executeScript<string>(script);
expect(videoLabel).toEqual('fake_device_0'); expect(videoLabel).toEqual('fake_device_0');
}); });
it('should replace the screen track with a custom virtual device', async () => { // TODO: Uncommented when Livekit allows to replace the screen track
const script = 'return document.getElementsByClassName("OV_video-element screen-type")[0].srcObject.getVideoTracks()[0].label;'; // 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.checkLayoutPresent();
await utils.checkToolbarIsPresent(); // 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); // let screenLabel = await browser.executeScript<string>(script);
expect(screenLabel).not.toEqual('custom_fake_screen'); // expect(screenLabel).not.toEqual('custom_fake_screen');
await utils.clickOn('#screenshare-btn'); // await utils.clickOn('#video-settings-btn-SCREEN');
await browser.sleep(500); // await browser.sleep(500);
await utils.waitForElement('#replace-screen-button'); // await utils.waitForElement('.video-settings-menu');
await utils.clickOn('#replace-screen-button'); // const replaceBtn = await utils.waitForElement('#replace-screen-button');
await browser.sleep(1000); // await replaceBtn.sendKeys(Key.ENTER);
screenLabel = await browser.executeScript<string>(script); // await browser.sleep(1000);
expect(screenLabel).toEqual('custom_fake_screen'); // 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(getBrowserOptionsWithoutDevices()) .setChromeOptions(getBrowserOptionsWithoutDevices())
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -124,56 +144,74 @@ describe('Media Devices: UI Behavior Without Media Device Permissions', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
let button = await utils.waitForElement('#camera-button'); let button = await utils.waitForElement('#camera-button');
expect(await button.isEnabled()).toBeFalse(); expect(await button.isEnabled()).toBeFalse();
button = await utils.waitForElement('#microphone-button'); button = await utils.waitForElement('#microphone-button');
expect(await button.isEnabled()).toBeFalse(); 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 browser.get(`${url}`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
await utils.clickOn('#join-button'); await utils.clickOn('#join-button');
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
let button = await utils.waitForElement('#camera-btn'); let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse(); expect(await button.isEnabled()).toBeFalse();
button = await utils.waitForElement('#mic-btn'); button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
let button = await utils.waitForElement('#camera-btn'); let button = await utils.waitForElement('#camera-btn');
expect(await button.isEnabled()).toBeFalse(); expect(await button.isEnabled()).toBeFalse();
button = await utils.waitForElement('#mic-btn'); button = await utils.waitForElement('#mic-btn');
expect(await button.isEnabled()).toBeFalse(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
// Open more options menu
await utils.togglePanel('settings'); await utils.togglePanel('settings');
await browser.sleep(500); await browser.sleep(500);
await utils.waitForElement('.settings-container'); await utils.waitForElement('.settings-container');
expect(await utils.isPresent('.settings-container')).toBeTrue(); expect(await utils.isPresent('.settings-container')).toBeTrue();
await utils.clickOn('#video-opt'); await utils.clickOn('#video-opt');
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue(); expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
let button = await utils.waitForElement('#camera-button'); let button = await utils.waitForElement('#camera-button');
expect(await button.isEnabled()).toBeFalse(); expect(await button.isEnabled()).toBeFalse();
await utils.clickOn('#audio-opt'); await utils.clickOn('#audio-opt');
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue(); expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
button = await utils.waitForElement('#microphone-button'); button = await utils.waitForElement('#microphone-button');
expect(await button.isEnabled()).toBeFalse(); expect(await button.isEnabled()).toBeFalse();
}); });

View File

@ -1,19 +1,20 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, Key, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -23,9 +24,6 @@ describe('Panels: UI Navigation and Section Switching', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });
@ -63,104 +61,164 @@ describe('Panels: UI Navigation and Section Switching', () => {
// expect(await utils.isPresent('#background-effects-container')).toBeFalse(); // 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
const chatButton = await utils.waitForElement('#chat-panel-btn'); const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click(); await chatButton.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.input-container'); await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue(); expect(await utils.isPresent('.input-container')).toBeTrue();
await utils.waitForElement('.messages-container'); await utils.waitForElement('.messages-container');
expect(await utils.isPresent('.messages-container')).toBeTrue(); expect(await utils.isPresent('.messages-container')).toBeTrue();
await chatButton.click(); await chatButton.click();
expect(await utils.isPresent('.input-container')).toBeFalse(); expect(await utils.isPresent('.input-container')).toBeFalse();
expect(await utils.isPresent('.messages-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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
const participantBtn = await utils.waitForElement('#participants-panel-btn'); const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click(); await participantBtn.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('.local-participant-container'); await utils.waitForElement('.local-participant-container');
expect(await utils.isPresent('.local-participant-container')).toBeTrue(); expect(await utils.isPresent('.local-participant-container')).toBeTrue();
await utils.waitForElement('ov-participant-panel-item'); await utils.waitForElement('ov-participant-panel-item');
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue(); expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
await participantBtn.click(); await participantBtn.click();
expect(await utils.isPresent('.local-participant-container')).toBeFalse(); expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Get activities button and click into it
const activitiesBtn = await utils.waitForElement('#activities-panel-btn'); const activitiesBtn = await utils.waitForElement('#activities-panel-btn');
await activitiesBtn.click(); await activitiesBtn.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
await utils.waitForElement('#activities-container'); await utils.waitForElement('#activities-container');
expect(await utils.isPresent('#activities-container')).toBeTrue(); expect(await utils.isPresent('#activities-container')).toBeTrue();
await utils.waitForElement('#recording-activity'); await utils.waitForElement('#recording-activity');
expect(await utils.isPresent('#recording-activity')).toBeTrue(); expect(await utils.isPresent('#recording-activity')).toBeTrue();
await activitiesBtn.click(); await activitiesBtn.click();
expect(await utils.isPresent('#activities-container')).toBeFalse(); expect(await utils.isPresent('#activities-container')).toBeFalse();
expect(await utils.isPresent('#recording-activity')).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; let element;
await browser.get(`${url}&prejoin=false`); await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
await utils.togglePanel('settings'); await utils.togglePanel('settings');
element = await utils.waitForElement('.sidenav-menu'); element = await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('#default-settings-panel')).toBeTrue(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkSessionIsPresent(); await utils.checkSessionIsPresent();
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
// Open chat panel
const chatButton = await utils.waitForElement('#chat-panel-btn'); const chatButton = await utils.waitForElement('#chat-panel-btn');
await chatButton.click(); await chatButton.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue(); expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
await utils.waitForElement('.input-container'); await utils.waitForElement('.input-container');
expect(await utils.isPresent('.input-container')).toBeTrue(); expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue(); expect(await utils.isPresent('.messages-container')).toBeTrue();
// Open participants panel
const participantBtn = await utils.waitForElement('#participants-panel-btn'); const participantBtn = await utils.waitForElement('#participants-panel-btn');
await participantBtn.click(); await participantBtn.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.local-participant-container')).toBeTrue(); expect(await utils.isPresent('.local-participant-container')).toBeTrue();
expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue(); expect(await utils.isPresent('ov-participant-panel-item')).toBeTrue();
// Switch to chat panel
await chatButton.click(); await chatButton.click();
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.input-container')).toBeTrue(); expect(await utils.isPresent('.input-container')).toBeTrue();
expect(await utils.isPresent('.messages-container')).toBeTrue(); expect(await utils.isPresent('.messages-container')).toBeTrue();
expect(await utils.isPresent('.local-participant-container')).toBeFalse(); expect(await utils.isPresent('.local-participant-container')).toBeFalse();
expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse(); expect(await utils.isPresent('ov-participant-panel-item')).toBeFalse();
// Close chat panel
await chatButton.click(); await chatButton.click();
expect(await utils.getNumberOfElements('.input-container')).toEqual(0); expect(await utils.getNumberOfElements('.input-container')).toEqual(0);
expect(await utils.isPresent('messages-container')).toBeFalse(); 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; let element;
await browser.get(`${url}&prejoin=false`); await browser.get(`${url}&prejoin=false`);
await utils.checkToolbarIsPresent(); await utils.checkToolbarIsPresent();
// Checking if toolbar is present
await utils.checkToolbarIsPresent();
// Open more options menu
await utils.togglePanel('settings'); await utils.togglePanel('settings');
await utils.waitForElement('.sidenav-menu'); await utils.waitForElement('.sidenav-menu');
expect(await utils.isPresent('.sidenav-menu')).toBeTrue(); expect(await utils.isPresent('.sidenav-menu')).toBeTrue();
await browser.sleep(500);
// Check if general section is shown
element = await utils.waitForElement('#general-opt'); element = await utils.waitForElement('#general-opt');
await element.click(); await element.click();
expect(await utils.isPresent('ov-participant-name-input')).toBeTrue(); expect(await utils.isPresent('ov-participant-name-input')).toBeTrue();
// Check if video section is shown
element = await utils.waitForElement('#video-opt'); element = await utils.waitForElement('#video-opt');
await element.click(); await element.click();
expect(await utils.isPresent('ov-video-devices-select')).toBeTrue(); expect(await utils.isPresent('ov-video-devices-select')).toBeTrue();
// Check if audio section is shown
element = await utils.waitForElement('#audio-opt'); element = await utils.waitForElement('#audio-opt');
await element.click(); await element.click();
expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue(); expect(await utils.isPresent('ov-audio-devices-select')).toBeTrue();
}); });
}); });

View File

@ -1,19 +1,19 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -23,89 +23,95 @@ describe('E2E: Screensharing features', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Enable screensharing // Clicking to screensharing button
await utils.waitForElement('#screenshare-btn'); await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await browser.sleep(500); await browser.sleep(500);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2); expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable screensharing // expect(await utils.getNumberOfElements('.OV_stream.speaking')).toEqual(1);
await utils.disableScreenShare(); await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1); expect(await utils.getNumberOfElements('video')).toEqual(1);
// Enable again // toggle screenshare again
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await browser.sleep(500); await browser.sleep(500);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2); expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable again
await utils.disableScreenShare(); await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1); 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Mute camera
await utils.waitForElement('#camera-btn'); await utils.waitForElement('#camera-btn');
await utils.clickOn('#camera-btn'); await utils.clickOn('#camera-btn');
// Enable screensharing // Clicking to screensharing button
const screenshareButton = await utils.waitForElement('#screenshare-btn'); const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue(); expect(await screenshareButton.isDisplayed()).toBeTrue();
await screenshareButton.click(); await screenshareButton.click();
await browser.sleep(500); await browser.sleep(500);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('video')).toEqual(2); expect(await utils.getNumberOfElements('video')).toEqual(2);
// Disable screensharing
await utils.disableScreenShare(); await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(1); 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Enable screensharing // Clicking to screensharing button
const screenshareButton = await utils.waitForElement('#screenshare-btn'); const screenshareButton = await utils.waitForElement('#screenshare-btn');
expect(await screenshareButton.isDisplayed()).toBeTrue(); expect(await screenshareButton.isDisplayed()).toBeTrue();
await screenshareButton.click(); await screenshareButton.click();
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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 roomName = 'screensharingE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// First participant screenshares // Clicking to screensharing button
await utils.waitForElement('#screenshare-btn'); await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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}")`; const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript); await browser.executeScript(newTabScript);
const tabs = await browser.getAllWindowHandles(); const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[1]); await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn'); await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await browser.sleep(500); await browser.sleep(500);
@ -113,7 +119,7 @@ describe('E2E: Screensharing features', () => {
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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.switchTo().window(tabs[0]);
await browser.sleep(500); await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4); expect(await utils.getNumberOfElements('video')).toEqual(4);
@ -121,37 +127,39 @@ describe('E2E: Screensharing features', () => {
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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 roomName = 'screensharingtwoE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// First participant screenshares // Clicking to screensharing button
await utils.waitForElement('#screenshare-btn'); await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await browser.sleep(500); await browser.sleep(500);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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); const tabs = await utils.openTab(fixedUrl);
await browser.switchTo().window(tabs[1]); await browser.switchTo().window(tabs[1]);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
// Clicking to screensharing button
await utils.waitForElement('#screenshare-btn'); await utils.waitForElement('#screenshare-btn');
await utils.clickOn('#screenshare-btn'); await utils.clickOn('#screenshare-btn');
await browser.sleep(500); await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(4); expect(await utils.getNumberOfElements('video')).toEqual(4);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
// Disable screensharing
// Disable screensharing for second participant
await utils.disableScreenShare(); await utils.disableScreenShare();
expect(await utils.getNumberOfElements('video')).toEqual(3); expect(await utils.getNumberOfElements('video')).toEqual(3);
await utils.waitForElement('.OV_big'); await utils.waitForElement('.OV_big');
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); 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.switchTo().window(tabs[0]);
await browser.sleep(500); await browser.sleep(500);
expect(await utils.getNumberOfElements('video')).toEqual(3); expect(await utils.getNumberOfElements('video')).toEqual(3);
@ -159,52 +167,38 @@ describe('E2E: Screensharing features', () => {
expect(await utils.getNumberOfElements('.OV_big')).toEqual(1); expect(await utils.getNumberOfElements('.OV_big')).toEqual(1);
}); });
it('should correctly share screen with microphone muted and maintain proper track state', async () => { // it('should screensharing with audio muted', async () => {
// Helper for inspecting stream tracks // let isAudioEnabled;
const getMediaTracks = (className: string) => { // const getAudioScript = (className: string) => {
return ` // return `return document.getElementsByClassName('${className}')[0].srcObject.getAudioTracks()[0].enabled;`;
const tracks = document.getElementsByClassName('${className}')[0].srcObject.getTracks(); // };
return tracks.map(track => ({ // await browser.get(`${url}&prejoin=false`);
kind: track.kind,
enabled: track.enabled,
id: track.id,
label: track.label
}));`;
};
// Setup: Navigate to room and skip prejoin // await utils.checkLayoutPresent();
await browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent();
// Step 1: First mute the microphone // const micButton = await utils.waitForElement('#mic-btn');
const micButton = await utils.waitForElement('#mic-btn'); // await micButton.click();
await micButton.click();
// Step 2: Start screen sharing // // Clicking to screensharing button
await utils.clickOn('#screenshare-btn'); // 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');
await utils.waitForElement('.screen-type'); // expect(await utils.getNumberOfElements('video')).toEqual(2);
expect(await utils.getNumberOfElements('video')).toEqual(2);
// Step 4: Verify screen share track properties // isAudioEnabled = await browser.executeScript(getAudioScript('screen-type'));
const screenTracks: any[] = await browser.executeScript(getMediaTracks('screen-type')); // expect(isAudioEnabled).toBeFalse();
expect(screenTracks.length).toEqual(1);
expect(screenTracks[0].kind).toEqual('video');
expect(screenTracks[0].enabled).toBeTrue();
// Step 5: Verify microphone status indicators for both streams // await utils.waitForElement('#status-mic');
// await utils.waitForElement('#status-mic'); // expect(await utils.getNumberOfElements('#status-mic')).toEqual(2);
// const micStatusCount = await utils.getNumberOfElements('#status-mic');
// expect(micStatusCount).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 () => { // it('should show and hide CAMERA stream when muting video with screensharing', async () => {
// await browser.get(`${url}&prejoin=false`); // await browser.get(`${url}&prejoin=false`);

View File

@ -1,18 +1,19 @@
import { Builder, ILocation, IRectangle, ISize, WebDriver } from 'selenium-webdriver'; import { Builder, ILocation, IRectangle, ISize, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -22,13 +23,10 @@ describe('Stream rendering and media toggling scenarios', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}&prejoin=true&videoEnabled=false`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -41,7 +39,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(1); 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 browser.get(`${url}&prejoin=true&audioEnabled=false`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -54,7 +52,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); 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 browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -67,7 +65,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(1); expect(await utils.getNumberOfElements('audio')).toEqual(1);
}); });
it('should add a screen share video/audio when sharing screen with both camera and mic muted', async () => { 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 browser.get(`${url}&prejoin=true&videoEnabled=false&audioEnabled=false`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -92,7 +90,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); expect(await utils.getNumberOfElements('audio')).toEqual(0);
}); });
it('should add a screen share video/audio when sharing screen with both camera and mic enabled', async () => { 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 browser.get(`${url}&prejoin=true&videoEnabled=true&audioEnabled=true`);
await utils.checkPrejoinIsPresent(); await utils.checkPrejoinIsPresent();
@ -117,9 +115,9 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(1); 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 roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
@ -147,7 +145,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); expect(await utils.getNumberOfElements('audio')).toEqual(0);
}); });
it('should render two video elements and no audio when two participants join with audio muted', async () => { it('should show two video elements when a two participants join with audio muted', async () => {
const roomName = `streams-${Date.now()}`; const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
@ -160,8 +158,6 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); expect(await utils.getNumberOfElements('audio')).toEqual(0);
const tabs = await utils.openTab(fixedUrl); const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]); await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote'); await utils.waitForElement('.OV_stream.remote');
@ -177,7 +173,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); 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 roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
@ -190,7 +186,6 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(1); expect(await utils.getNumberOfElements('audio')).toEqual(1);
const tabs = await utils.openTab(fixedUrl); const tabs = await utils.openTab(fixedUrl);
await browser.sleep(1000);
await browser.switchTo().window(tabs[0]); await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote'); await utils.waitForElement('.OV_stream.remote');
@ -206,7 +201,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(2); 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 roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
@ -245,7 +240,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(0); expect(await utils.getNumberOfElements('audio')).toEqual(0);
}); });
it('should add a screen share video/audio when a remote participant with both video and audio enabled shares their screen', async () => { it('should show 3 video elements when a REMOTE participant shares its screen', async () => {
const roomName = `streams-${Date.now()}`; const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=true&audioEnabled=true`;
await browser.get(fixedUrl); await browser.get(fixedUrl);
@ -284,7 +279,7 @@ describe('Stream rendering and media toggling scenarios', () => {
expect(await utils.getNumberOfElements('audio')).toEqual(2); expect(await utils.getNumberOfElements('audio')).toEqual(2);
}); });
it('should add a screen share video/audio for both participants when both share their screen with video/audio muted', async () => { it('should show 4 video elements when a two participants share theirs screen', async () => {
const roomName = `streams-${Date.now()}`; const roomName = `streams-${Date.now()}`;
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false&videoEnabled=false&audioEnabled=false`;
await browser.get(fixedUrl); 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -346,9 +341,6 @@ describe('Stream UI controls and interaction features', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });
@ -666,7 +658,7 @@ describe('Stream UI controls and interaction features', () => {
expect(streamProps.y).toEqual(0); 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 roomName = 'speakingE2E';
const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`; const fixedUrl = `${url}&roomName=${roomName}&prejoin=false`;
await browser.get(`${fixedUrl}&audioEnabled=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 // Starting new browser for adding the second participant
const newTabScript = `window.open("${fixedUrl}")`; const newTabScript = `window.open("${fixedUrl}")`;
await browser.executeScript(newTabScript); await browser.executeScript(newTabScript);
await browser.sleep(1000);
const tabs = await browser.getAllWindowHandles(); const tabs = await browser.getAllWindowHandles();
await browser.switchTo().window(tabs[0]); await browser.switchTo().window(tabs[0]);
await utils.waitForElement('.OV_stream.remote.speaking'); await utils.waitForElement('.OV_stream.remote.speaking');
expect(await utils.getNumberOfElements('.OV_stream.remote.speaking')).toEqual(1); 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); 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -709,9 +696,6 @@ describe('Video playback reliability with different media states', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); await browser.quit();
}); });

View File

@ -1,18 +1,19 @@
import { Builder, WebDriver } from 'selenium-webdriver'; import { Builder, WebDriver } from 'selenium-webdriver';
import { TestAppConfig } from './selenium.conf'; import { OPENVIDU_CALL_SERVER } from '../config';
import { OpenViduComponentsPO } from './utils.po.test'; 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 browser: WebDriver;
let utils: OpenViduComponentsPO; let utils: OpenViduComponentsPO;
async function createChromeBrowser(): Promise<WebDriver> { async function createChromeBrowser(): Promise<WebDriver> {
return await new Builder() return await new Builder()
.forBrowser(TestAppConfig.browserName) .forBrowser(WebComponentConfig.browserName)
.withCapabilities(TestAppConfig.browserCapabilities) .withCapabilities(WebComponentConfig.browserCapabilities)
.setChromeOptions(TestAppConfig.browserOptions) .setChromeOptions(WebComponentConfig.browserOptions)
.usingServer(TestAppConfig.seleniumAddress) .usingServer(WebComponentConfig.seleniumAddress)
.build(); .build();
} }
@ -22,13 +23,10 @@ describe('Toolbar button functionality for local media control', () => {
}); });
afterEach(async () => { afterEach(async () => {
try {
await utils.leaveRoom();
} catch (error) {}
await browser.quit(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();
@ -45,7 +43,7 @@ describe('Toolbar button functionality for local media control', () => {
expect(await utils.isPresent('#mic-btn #mic')).toBeTrue(); 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 browser.get(`${url}&prejoin=false`);
await utils.checkLayoutPresent(); await utils.checkLayoutPresent();

View File

@ -6,7 +6,7 @@ if [[ -z "$BASEHREF_VERSION" ]]; then
fi fi
# Replace version from "stable" to the specified one in all TypeDoc links # 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 # Replace testapp README by openvidu-components-angular README
mv README.md README-testapp.md mv README.md README-testapp.md
@ -16,7 +16,7 @@ cp ./projects/openvidu-components-angular/README.md .
npm run doc:build npm run doc:build
# Return links to "stable" version # 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 # Undo changes with READMEs
rm README.md rm README.md

View File

@ -204,6 +204,7 @@ npm install openvidu-components-angular@3.0.0
- `publishScreen` and `unpublishScreen` methods have been renamed to `setScreenShareEnabled` - `publishScreen` and `unpublishScreen` methods have been renamed to `setScreenShareEnabled`
- `isSttReadyObs` observable has been deleted - `isSttReadyObs` observable has been deleted
- `connectRoom` method has been added - `connectRoom` method has been added
- `getRoomMetadata` method has been added to get the metadata of the room
- `getRoomName` method has been added to get the name of the room - `getRoomName` method has been added to get the name of the room
- ##### Panel Service (`PanelService`): - ##### Panel Service (`PanelService`):

View File

@ -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

View File

@ -1,33 +1,35 @@
{ {
"dependencies": { "dependencies": {
"@angular/animations": "19.2.8", "@angular/animations": "18.2.5",
"@angular/cdk": "19.2.11", "@angular/cdk": "18.2.5",
"@angular/common": "19.2.8", "@angular/common": "18.2.5",
"@angular/core": "19.2.8", "@angular/core": "18.2.5",
"@angular/forms": "19.2.8", "@angular/forms": "18.2.5",
"@angular/material": "19.2.11", "@angular/material": "18.2.5",
"@angular/platform-browser": "19.2.8", "@angular/platform-browser": "18.2.5",
"@angular/platform-browser-dynamic": "19.2.8", "@angular/platform-browser-dynamic": "18.2.5",
"@angular/router": "19.2.8", "@angular/router": "18.2.5",
"@livekit/track-processors": "^0.5.6", "@livekit/track-processors": "0.3.2",
"@types/dom-mediacapture-transform": "^0.1.11",
"autolinker": "4.0.0", "autolinker": "4.0.0",
"livekit-client": "2.11.4", "livekit-client": "2.5.2",
"rxjs": "7.8.1", "rxjs": "7.8.1",
"tslib": "2.7.0", "tslib": "2.7.0",
"zone.js": "^0.15.0" "zone.js": "^0.14.6"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "19.2.9", "@angular-devkit/build-angular": "18.2.5",
"@angular/cli": "19.2.9", "@angular/cli": "18.2.5",
"@angular/compiler": "19.2.8", "@angular/compiler": "18.2.5",
"@angular/compiler-cli": "19.2.8", "@angular/compiler-cli": "18.2.5",
"@angular/elements": "18.2.5",
"@compodoc/compodoc": "^1.1.25", "@compodoc/compodoc": "^1.1.25",
"@types/dom-mediacapture-transform": "0.1.9",
"@types/dom-webcodecs": "0.1.11",
"@types/jasmine": "^5.1.4", "@types/jasmine": "^5.1.4",
"@types/node": "20.12.14", "@types/node": "20.12.14",
"@types/selenium-webdriver": "4.1.16", "@types/selenium-webdriver": "4.1.16",
"@types/ws": "^8.5.12", "@types/ws": "^8.5.12",
"chromedriver": "138.0.0", "chromedriver": "129.0.0",
"concat": "^1.0.3", "concat": "^1.0.3",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
@ -47,13 +49,13 @@
"karma-mocha-reporter": "2.2.5", "karma-mocha-reporter": "2.2.5",
"karma-notify-reporter": "1.3.0", "karma-notify-reporter": "1.3.0",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"ng-packagr": "19.2.2", "ng-packagr": "18.2.1",
"npm-watch": "^0.13.0", "npm-watch": "^0.13.0",
"prettier": "3.3.3", "prettier": "3.3.3",
"selenium-webdriver": "4.32.0", "selenium-webdriver": "4.25.0",
"ts-node": "10.9.2", "ts-node": "10.9.2",
"tslint": "6.1.3", "tslint": "6.1.3",
"typescript": "5.8.3", "typescript": "5.4.5",
"webpack-bundle-analyzer": "^4.10.2" "webpack-bundle-analyzer": "^4.10.2"
}, },
"name": "openvidu-components-testapp", "name": "openvidu-components-testapp",
@ -73,32 +75,36 @@
"start-prod": "npx http-server ./dist/openvidu-components-testapp/browser --port 4200", "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", "start:ssl": "ng serve --ssl --configuration development --host 0.0.0.0 --port 5080",
"build": "ng build openvidu-components-testapp --configuration production", "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: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-directives-tutorials": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tutorials.js",
"doc:generate-directive-tables": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tables.js", "doc:generate-directive-tables": "node ./projects/openvidu-components-angular/doc/scripts/generate-directive-tables.js",
"doc:clean-copy": "rm -rf ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular && cp -r ./docs/openvidu-components-angular/ ../../openvidu.io/docs/docs/reference-docs/openvidu-components-angular", "doc:clean-copy": "rm -rf ../../openvidu-docs/docs/docs/reference-docs/openvidu-components-angular && cp -r ./docs/openvidu-components-angular/ ../../openvidu-docs/docs/docs/reference-docs/openvidu-components-angular",
"doc:serve": "npx compodoc -c ../openvidu-components-angular/projects/openvidu-components-angular/doc/.compodocrc.json --serve --port 7000", "doc:serve": "npx compodoc -c ../openvidu-components-angular/projects/openvidu-components-angular/doc/.compodocrc.json --serve --port 7000",
"doc:serve-watch": "npm-watch doc:serve", "doc:serve-watch": "npm-watch doc:serve",
"lib:serve": "ng build openvidu-components-angular --watch", "lib:serve": "ng build openvidu-components-angular --watch",
"lib:build": "ng build openvidu-components-angular --configuration production && cd ./dist/openvidu-components-angular", "lib:build": "ng build openvidu-components-angular --configuration production && cd ./dist/openvidu-components-angular",
"lib:pack": "cd ./dist/openvidu-components-angular && npm pack", "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", "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-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-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-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/directives.test.js",
"e2e:nested-attribute-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/nested-components/attribute-directives.test.js", "e2e:webcomponent-all": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/**/*.test.js",
"e2e:lib-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/api-directives.test.js", "e2e:webcomponent-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/api-directives.test.js",
"e2e:lib-internal-directives": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/internal-directives.test.js", "e2e:webcomponent-captions": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/captions.test.js",
"e2e:lib-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/chat.test.js", "e2e:webcomponent-chat": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/chat.test.js",
"e2e:lib-events": "tsc --project ./e2e && npx jasmine ./e2e/dist/events.test.js", "e2e:webcomponent-events": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/events.test.js",
"e2e:lib-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/media-devices.test.js", "e2e:webcomponent-media-devices": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/media-devices.test.js",
"e2e:lib-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/panels.test.js", "e2e:webcomponent-panels": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/panels.test.js",
"e2e:lib-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/screensharing.test.js", "e2e:webcomponent-screensharing": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/screensharing.test.js",
"e2e:lib-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/stream.test.js", "e2e:webcomponent-stream": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/webcomponent-e2e/stream.test.js",
"e2e:lib-toolbar": "tsc --project ./e2e && npx jasmine --fail-fast ./e2e/dist/toolbar.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", "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" "husky": "cd .. && husky install"
}, },
"version": "3.3.0" "version": "3.0.0-beta3"
} }

View File

@ -8,7 +8,7 @@ Angular Components are the simplest way to create real-time videoconferencing ap
## Getting Started ## 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) 1. Create an Angular Project (>= 17.0.0)
@ -54,28 +54,24 @@ You can also customize the styles in your `styles.scss` file:
```scss ```scss
:root { :root {
/* Basic colors */ --ov-primary-color: #303030;
--ov-background-color: #303030; // Background color --ov-secondary-color: #3e3f3f;
--ov-surface-color: #ffffff; // Surfaces colors (panels, dialogs) --ov-tertiary-color: #598eff;
--ov-warn-color: #eb5144;
--ov-accent-color: #ffae35;
--ov-light-color: #e6e6e6;
/* Text colors */ --ov-logo-background-color: #3a3d3d;
--ov-text-primary-color: #ffffff; // Text color over primary background
--ov-text-surface-color: #1d1d1d; // Text color over surface background
/* Action colors */ --ov-text-color: #ffffff;
--ov-primary-action-color: #273235; // Primary color for buttons, etc.
--ov-secondary-action-color: #f1f1f1; // Secondary color for buttons, etc.
--ov-accent-action-color: #0089ab; // Color for highlighted elements
/* Status colors */ --ov-panel-text-color: #1d1d1d;
--ov-error-color: #eb5144; // Error color --ov-panel-background: #ffffff;
--ov-warn-color: #ffba53; // Warning color
/* Radius */ --ov-buttons-radius: 50%;
--ov-toolbar-buttons-radius: 50%; // Radius for toolbar buttons --ov-leave-button-radius: 10px;
--ov-leave-button-radius: 10px; // Radius for leave button --ov-video-radius: 5px;
--ov-video-radius: 5px; // Radius for video elements --ov-panel-radius: 5px;
--ov-surface-radius: 5px; // Radius for surface elements
} }
``` ```
@ -92,7 +88,7 @@ You can also customize the styles in your `styles.scss` file:
## API Documentation ## 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 ## Support

View File

@ -3,334 +3,168 @@ const glob = require('glob');
const startApiLine = '<!-- start-dynamic-api-directives-content -->'; const startApiLine = '<!-- start-dynamic-api-directives-content -->';
const apiDirectivesTable = const apiDirectivesTable =
'| **Parameter** | **Type** | **Reference** | \n' + '| **Parameter** | **Type** | **Reference** | \n' +
'|:--------------------------------: | :-------: | :---------------------------------------------: |'; '|:--------------------------------: | :-------: | :---------------------------------------------: |';
const endApiLine = '<!-- end-dynamic-api-directives-content -->'; const endApiLine = '<!-- end-dynamic-api-directives-content -->';
/**
* Get all directive files from the API directives directory
*/
function getDirectiveFiles() { function getDirectiveFiles() {
const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api'; // Directory where directive files are located
return listFiles(directivesDir, '.directive.ts'); const directivesDir = 'projects/openvidu-components-angular/src/lib/directives/api';
return listFiles(directivesDir, '.directive.ts');
} }
/**
* Get all component files
*/
function getComponentFiles() { function getComponentFiles() {
const componentsDir = 'projects/openvidu-components-angular/src/lib/components'; // Directory where component files are located
return listFiles(componentsDir, '.component.ts'); const componentsDir = 'projects/openvidu-components-angular/src/lib/components';
return listFiles(componentsDir, '.component.ts');
} }
/**
* Get all admin files
*/
function getAdminFiles() { function getAdminFiles() {
const componentsDir = 'projects/openvidu-components-angular/src/lib/admin'; // Directory where component files are located
return listFiles(componentsDir, '.component.ts'); 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) { function listFiles(directoryPath, fileExtension) {
const files = glob.sync(`${directoryPath}/**/*${fileExtension}`); const files = glob.sync(`${directoryPath}/**/*${fileExtension}`);
if (files.length === 0) { if (files.length === 0) {
throw new Error(`No ${fileExtension} files found in ${directoryPath}`); throw new Error(`No ${fileExtension} files found in ${directoryPath}`);
} }
return files; 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) { function initializeDynamicTableContent(filePath) {
replaceDynamicTableContent(filePath, apiDirectivesTable); replaceDynamicTableContent(filePath, apiDirectivesTable);
} }
/**
* Replace table content with "no directives" message
*/
function removeApiTableContent(filePath) { function removeApiTableContent(filePath) {
const content = '_No API directives available for this component_. \n'; const content = '_No API directives available for this component_. \n';
replaceDynamicTableContent(filePath, content); replaceDynamicTableContent(filePath, content);
} }
/** function apiTableContentIsEmpty(filePath) {
* Add a row to the markdown table 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) { function addRowToTable(filePath, parameter, type, reference) {
try { // Read the current content of the file
const data = fs.readFileSync(filePath, 'utf8'); try {
const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`; const data = fs.readFileSync(filePath, 'utf8');
const lines = data.split('\n'); // Define the target line and the Markdown row
const targetIndex = lines.findIndex((line) => line.includes(endApiLine)); const markdownRow = `| **${parameter}** | \`${type}\` | [${reference}](../directives/${reference}.html) |`;
if (targetIndex !== -1) { // Find the line that contains the table
lines.splice(targetIndex, 0, markdownRow); const lines = data.split('\n');
const updatedContent = lines.join('\n'); const targetIndex = lines.findIndex((line) => line.includes(endApiLine));
fs.writeFileSync(filePath, updatedContent, 'utf8');
console.log(`Added directive: ${parameter} -> ${reference}`); if (targetIndex !== -1) {
} else { // Insert the new row above the target line
console.error('End marker not found in file:', filePath); lines.splice(targetIndex, 0, markdownRow);
}
} catch (error) { // Join the lines back together
console.error('Error adding row to table:', error); const updatedContent = lines.join('\n');
}
// Write the updated content to the file
fs.writeFileSync(filePath, updatedContent, 'utf8');
console.log('Row added successfully.');
} else {
console.error('Table not found in the file.');
}
} catch (error) {
console.error('Error writing to file:', error);
}
} }
/**
* Replace content between start and end markers
*/
function replaceDynamicTableContent(filePath, content) { function replaceDynamicTableContent(filePath, content) {
try { // Read the current content of the file
const data = fs.readFileSync(filePath, 'utf8'); try {
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g'); const data = fs.readFileSync(filePath, 'utf8');
const pattern = new RegExp(`${startApiLine}([\\s\\S]*?)${endApiLine}`, 'g');
const modifiedContent = data.replace(pattern, (match, capturedContent) => { // Replace the content between startLine and endLine with the replacement table
return startApiLine + '\n' + content + '\n' + endApiLine; const modifiedContent = data.replace(pattern, (match, capturedContent) => {
}); return startApiLine + '\n' + content + '\n' + endApiLine;
});
fs.writeFileSync(filePath, modifiedContent, 'utf8'); // Write the modified content back to the file
console.log(`Updated table content in: ${filePath}`); fs.writeFileSync(filePath, modifiedContent, 'utf8');
} catch (error) { } catch (error) {
if (error.code === 'ENOENT') { if (error.code === 'ENOENT') {
console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`); console.log(`${filePath} not found! Maybe it is an internal component. Skipping...`);
} else { } else {
console.error('Error writing to file:', error); console.error('Error writing to file:', error);
} }
} }
} }
// Main execution const directiveFiles = getDirectiveFiles();
if (require.main === module) { const componentFiles = getComponentFiles();
try { const adminFiles = getAdminFiles();
const directiveFiles = getDirectiveFiles(); writeApiDirectivesTable(componentFiles.concat(adminFiles), directiveFiles);
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
};

View File

@ -5,7 +5,8 @@
"../src/lib/directives/**/*.ts", "../src/lib/directives/**/*.ts",
"../src/lib/services/**/*.ts", "../src/lib/services/**/*.ts",
"../src/lib/models/**/*.ts", "../src/lib/models/**/*.ts",
"../src/lib/pipes/**/*.ts" "../src/lib/pipes/**/*.ts",
// "../../../src/app/openvidu-webcomponent/**/*.ts",
], ],
"exclude": [ "exclude": [
"src/test.ts", "src/test.ts",

View File

@ -1,418 +0,0 @@
{
"name": "openvidu-components-angular",
"version": "3.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "openvidu-components-angular",
"version": "3.3.0",
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@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",
"@livekit/track-processors": "^0.3.2",
"autolinker": "^4.0.0",
"buffer": "^6.0.3",
"livekit-client": "^2.1.0"
}
},
"node_modules/@angular/animations": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-18.2.8.tgz",
"integrity": "sha512-dMSn2hg70siv3lhP+vqhMbgc923xw6XBUvnpCPEzhZqFHvPXfh/LubmsD5RtqHmjWebXtgVcgS+zg3Gq3jB2lg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.2.8"
}
},
"node_modules/@angular/cdk": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-18.2.8.tgz",
"integrity": "sha512-J8A2FkwTBzLleAEWz6EgW73dEoeq87GREBPjTv8+2JV09LX+V3hnbgNk6zWq5k4OXtQNg9WrWP9QyRbUyA597g==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"optionalDependencies": {
"parse5": "^7.1.2"
},
"peerDependencies": {
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/common": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-18.2.8.tgz",
"integrity": "sha512-TYsKtE5nVaIScWSLGSO34Skc+s3hB/BujSddnfQHoNFvPT/WR0dfmdlpVCTeLj+f50htFoMhW11tW99PbK+whQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/core": "18.2.8",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/core": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-18.2.8.tgz",
"integrity": "sha512-NwIuX/Iby1jT6Iv1/s6S3wOFf8xfuQR3MPGvKhGgNtjXLbHG+TXceK9+QPZC0s9/Z8JR/hz+li34B79GrIKgUg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"rxjs": "^6.5.3 || ^7.4.0",
"zone.js": "~0.14.10"
}
},
"node_modules/@angular/forms": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-18.2.8.tgz",
"integrity": "sha512-JCLki7KC6D5vF6dE6yGlBmW33khIgpHs8N9SzuiJtkQqNDTIQA8cPsGV6qpLpxflxASynQOX5lDkWYdQyfm77Q==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/common": "18.2.8",
"@angular/core": "18.2.8",
"@angular/platform-browser": "18.2.8",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/material": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-18.2.8.tgz",
"integrity": "sha512-wQGMVsfQ9lQfih2VsWAvV4z3S3uBxrxc61owlE+K0T1BxH9u/jo3A/rnRitIdvR/L4NnYlfhCnmrW9K+Pl+WCg==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"peerDependencies": {
"@angular/animations": "^18.0.0 || ^19.0.0",
"@angular/cdk": "18.2.8",
"@angular/common": "^18.0.0 || ^19.0.0",
"@angular/core": "^18.0.0 || ^19.0.0",
"@angular/forms": "^18.0.0 || ^19.0.0",
"@angular/platform-browser": "^18.0.0 || ^19.0.0",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "18.2.8",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-18.2.8.tgz",
"integrity": "sha512-EPai4ZPqSq3ilLJUC85kPi9wo5j5suQovwtgRyjM/75D9Qy4TV19g8hkVM5Co/zrltO8a2G6vDscCNI5BeGw2A==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0"
},
"peerDependencies": {
"@angular/animations": "18.2.8",
"@angular/common": "18.2.8",
"@angular/core": "18.2.8"
},
"peerDependenciesMeta": {
"@angular/animations": {
"optional": true
}
}
},
"node_modules/@bufbuild/protobuf": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-1.10.0.tgz",
"integrity": "sha512-QDdVFLoN93Zjg36NoQPZfsVH9tZew7wKDKyV5qRdj8ntT4wQCOradQjRaTdwMhWUYsgKsvCINKKm87FdEk96Ag==",
"license": "(Apache-2.0 AND BSD-3-Clause)",
"peer": true
},
"node_modules/@livekit/protocol": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@livekit/protocol/-/protocol-1.24.0.tgz",
"integrity": "sha512-9dCsqnkMn7lvbI4NGh18zhLDsrXyUcpS++TEFgEk5Xv1WM3R2kT3EzqgL1P/mr3jaabM6rJ8wZA/KJLuQNpF5w==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@bufbuild/protobuf": "^1.10.0"
}
},
"node_modules/@livekit/track-processors": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@livekit/track-processors/-/track-processors-0.3.2.tgz",
"integrity": "sha512-4JUCzb7yIKoVsTo8J6FTzLZJHcI6DihfX/pGRDg0SOGaxprcDPrt8jaDBBTsnGBSXHeMxl2ugN+xQjdCWzLKEA==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@mediapipe/holistic": "0.5.1675471629",
"@mediapipe/tasks-vision": "0.10.9"
},
"peerDependencies": {
"livekit-client": "^1.12.0 || ^2.1.0"
}
},
"node_modules/@mediapipe/holistic": {
"version": "0.5.1675471629",
"resolved": "https://registry.npmjs.org/@mediapipe/holistic/-/holistic-0.5.1675471629.tgz",
"integrity": "sha512-qY+cxtDeSOvVtevrLgnodiwXYaAtPi7dHZtNv/bUCGEjFicAOYtMmrZSqMmbPkTB2+4jLnPF1vgshkAqQRSYAw==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/@mediapipe/tasks-vision": {
"version": "0.10.9",
"resolved": "https://registry.npmjs.org/@mediapipe/tasks-vision/-/tasks-vision-0.10.9.tgz",
"integrity": "sha512-/gFguyJm1ng4Qr7VVH2vKO+zZcQd8wc3YafUfvBuYFX0Y5+CvrV+VNPEVkl5W/gUZF5KNKNZAiaHPULGPCIjyQ==",
"license": "Apache-2.0",
"peer": true
},
"node_modules/autolinker": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/autolinker/-/autolinker-4.0.0.tgz",
"integrity": "sha512-fl5Kh6BmEEZx+IWBfEirnRUU5+cOiV0OK7PEt0RBKvJMJ8GaRseIOeDU3FKf4j3CE5HVefcjHmhYPOcaVt0bZw==",
"license": "MIT",
"peer": true,
"dependencies": {
"tslib": "^2.3.0"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"optional": true,
"peer": true,
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause",
"peer": true
},
"node_modules/livekit-client": {
"version": "2.5.9",
"resolved": "https://registry.npmjs.org/livekit-client/-/livekit-client-2.5.9.tgz",
"integrity": "sha512-oDpK6SKYB1F+mNO+25DA0bF0cD2XoOJeD8ji4YQpzDBQv2IxeyKrQhoqXAqrYgIKuiMNkImSf+yg2v7EHSl4Og==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"@livekit/protocol": "1.24.0",
"events": "^3.3.0",
"loglevel": "^1.8.0",
"sdp-transform": "^2.14.1",
"ts-debounce": "^4.0.0",
"tslib": "2.7.0",
"typed-emitter": "^2.1.0",
"webrtc-adapter": "^9.0.0"
}
},
"node_modules/loglevel": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz",
"integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/loglevel"
}
},
"node_modules/parse5": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.0.tgz",
"integrity": "sha512-ZkDsAOcxsUMZ4Lz5fVciOehNcJ+Gb8gTzcA4yl3wnc273BAybYWrQ+Ks/OjCjSEpjvQkDSeZbybK9qj2VHHdGA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"entities": "^4.5.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/rxjs": {
"version": "7.8.1",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz",
"integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/sdp": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==",
"license": "MIT",
"peer": true
},
"node_modules/sdp-transform": {
"version": "2.14.2",
"resolved": "https://registry.npmjs.org/sdp-transform/-/sdp-transform-2.14.2.tgz",
"integrity": "sha512-icY6jVao7MfKCieyo1AyxFYm1baiM+fA00qW/KrNNVlkxHAd34riEKuEkUe4bBb3gJwLJZM+xT60Yj1QL8rHiA==",
"license": "MIT",
"peer": true,
"bin": {
"sdp-verify": "checker.js"
}
},
"node_modules/ts-debounce": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ts-debounce/-/ts-debounce-4.0.0.tgz",
"integrity": "sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==",
"license": "MIT",
"peer": true
},
"node_modules/tslib": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
"integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
"license": "0BSD"
},
"node_modules/typed-emitter": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typed-emitter/-/typed-emitter-2.1.0.tgz",
"integrity": "sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==",
"license": "MIT",
"peer": true,
"optionalDependencies": {
"rxjs": "*"
}
},
"node_modules/webrtc-adapter": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.1.tgz",
"integrity": "sha512-1AQO+d4ElfVSXyzNVTOewgGT/tAomwwztX/6e3totvyyzXPvXIIuUUjAmyZGbKBKbZOXauuJooZm3g6IuFuiNQ==",
"license": "BSD-3-Clause",
"peer": true,
"dependencies": {
"sdp": "^3.2.0"
},
"engines": {
"node": ">=6.0.0",
"npm": ">=3.10.0"
}
},
"node_modules/zone.js": {
"version": "0.14.10",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.14.10.tgz",
"integrity": "sha512-YGAhaO7J5ywOXW6InXNlLmfU194F8lVgu7bRntUF3TiG8Y3nBK0x1UJJuHUP/e8IyihkjCYqhCScpSwnlaSRkQ==",
"license": "MIT",
"peer": true
}
}
}

View File

@ -4,16 +4,16 @@
}, },
"name": "openvidu-components-angular", "name": "openvidu-components-angular",
"peerDependencies": { "peerDependencies": {
"@angular/animations": "^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 || ^19.0.0", "@angular/cdk": "^17.0.0 || ^18.0.0",
"@angular/common": "^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/common": "^17.0.0 || ^18.0.0",
"@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/core": "^17.0.0 || ^18.0.0",
"@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/forms": "^17.0.0 || ^18.0.0",
"@angular/material": "^17.0.0 || ^18.0.0 || ^19.0.0", "@angular/material": "^17.0.0 || ^18.0.0",
"autolinker": "^4.0.0", "autolinker": "^4.0.0",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"livekit-client": "^2.1.0", "livekit-client": "^2.1.0",
"@livekit/track-processors": "^0.3.2" "@livekit/track-processors": "^0.3.2"
}, },
"version": "3.3.0" "version": "3.0.0-beta3"
} }

View File

@ -4,6 +4,5 @@ With the following directives you can modify the default User Interface with the
<!-- start-dynamic-api-directives-content --> <!-- start-dynamic-api-directives-content -->
| **Parameter** | **Type** | **Reference** | | **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 --> <!-- end-dynamic-api-directives-content -->

View File

@ -4,16 +4,16 @@
.header { .header {
height: 50px; height: 50px;
background-color: var(--ov-secondary-action-color); background-color: var(--ov-secondary-color);
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.logout-btn { .logout-btn {
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.dashboard-body { .dashboard-body {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-secondary-color);
height: calc(100% - 75px); height: calc(100% - 75px);
} }
.toolbar-spacer { .toolbar-spacer {
@ -41,17 +41,17 @@
#sort-menu-btn { #sort-menu-btn {
margin-left: 5px; margin-left: 5px;
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
color: var(--ov-text-primary-color); color: var(--ov-panel-text-color);
} }
.search-bar { .search-bar {
height: 95%; height: 95%;
width: 30%; width: 30%;
display: flex; display: flex;
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
padding: 0px 10px; padding: 0px 10px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
#search-input { #search-input {
@ -87,7 +87,7 @@
} }
.recordings-container { .recordings-container {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-secondary-color);
height: calc(100% - 50px); height: calc(100% - 50px);
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
@ -106,7 +106,7 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
} }
} }
@ -135,18 +135,18 @@
margin-right: -50%; margin-right: -50%;
top: 50%; top: 50%;
left: 50%; left: 50%;
background-color: var(--ov-secondary-action-color); background-color: var(--ov-logo-background-color);
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
.video-btns button #play { .video-btns button #play {
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.video-btns button #download { .video-btns button #download {
color: var(--ov-accent-action-color); color: var(--ov-tertiary-color);
} }
.video-btns button #delete { .video-btns button #delete {
color: var(--ov-error-color); color: var(--ov-warn-color);
} }
.video-info-container > div { .video-info-container > div {
@ -167,7 +167,7 @@
.video-card-tag { .video-card-tag {
font-size: 13px; font-size: 13px;
color: var(--ov-text-primary-color); color: var(--ov-panel-text-color);
font-weight: bold; font-weight: bold;
} }
@ -204,16 +204,16 @@
transform: translateX(-50%); transform: translateX(-50%);
.load-more-btn { .load-more-btn {
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
color: var(--ov-text-primary-color); color: var(--ov-panel-text-color);
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
} }
.footer { .footer {
height: 25px; height: 25px;
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
color: var(--ov-text-primary-color); color: var(--ov-text-color);
position: absolute; position: absolute;
bottom: 0; bottom: 0;
left: 0; left: 0;
@ -225,7 +225,7 @@
gap: 2px; gap: 2px;
} }
.footer a { .footer a {
color: var(--ov-accent-action-color); color: var(--ov-tertiary-color);
padding-left: 5px; padding-left: 5px;
} }
@ -234,7 +234,7 @@
width: 100%; width: 100%;
display: table; display: table;
text-align: center; text-align: center;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex { ::ng-deep .mat-form-field-appearance-fill .mat-form-field-flex {

View File

@ -8,8 +8,7 @@ import { RecordingService } from '../../services/recording/recording.service';
@Component({ @Component({
selector: 'ov-admin-dashboard', selector: 'ov-admin-dashboard',
templateUrl: './admin-dashboard.component.html', templateUrl: './admin-dashboard.component.html',
styleUrls: ['./admin-dashboard.component.scss'], styleUrls: ['./admin-dashboard.component.scss']
standalone: false
}) })
export class AdminDashboardComponent implements OnInit, OnDestroy { export class AdminDashboardComponent implements OnInit, OnDestroy {
/** /**

View File

@ -6,5 +6,4 @@ With the following directives you can modify the default User Interface with the
| **Parameter** | **Type** | **Reference** | | **Parameter** | **Type** | **Reference** |
|:--------------------------------: | :-------: | :---------------------------------------------: | |:--------------------------------: | :-------: | :---------------------------------------------: |
| **error** | `any` | [AdminLoginErrorDirective](../directives/AdminLoginErrorDirective.html) | | **error** | `any` | [AdminLoginErrorDirective](../directives/AdminLoginErrorDirective.html) |
| **navbarTitle** | `any` | [AdminLoginTitleDirective](../directives/AdminLoginTitleDirective.html) |
<!-- end-dynamic-api-directives-content --> <!-- end-dynamic-api-directives-content -->

View File

@ -1,7 +1,7 @@
.header { .header {
height: 50px; height: 50px;
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.center-container { .center-container {
@ -9,12 +9,12 @@
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 100vh; height: 100vh;
background-color: var(--ov-secondary-action-color); background-color: var(--ov-secondary-color);
} }
.card-container { .card-container {
text-align: center; text-align: center;
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
padding: 10px; padding: 10px;
max-width: 300px; max-width: 300px;
min-width: 300px; min-width: 300px;
@ -23,8 +23,8 @@
} }
.form-btn { .form-btn {
background-color: var(--ov-accent-action-color) !important; background-color: var(--ov-tertiary-color) !important;
color: var(--ov-text-primary-color) !important; color: var(--ov-text-color) !important;
} }
.form-field, .form-field,
@ -33,5 +33,5 @@
} }
::ng-deep .mat-mdc-progress-spinner { ::ng-deep .mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: var(--ov-accent-action-color); --mdc-circular-progress-active-indicator-color: var(--ov-tertiary-color);
} }

View File

@ -7,8 +7,7 @@ import { OpenViduComponentsConfigService } from '../../services/config/directive
@Component({ @Component({
selector: 'ov-admin-login', selector: 'ov-admin-login',
templateUrl: './admin-login.component.html', templateUrl: './admin-login.component.html',
styleUrls: ['./admin-login.component.scss'], styleUrls: ['./admin-login.component.scss']
standalone: false
}) })
export class AdminLoginComponent implements OnInit { export class AdminLoginComponent implements OnInit {
/** /**

View File

@ -23,13 +23,13 @@
} }
.audio-container{ .audio-container{
background-color: var(--ov-accent-action-color); background-color: var(--ov-tertiary-color);
padding: 5px; padding: 5px;
max-width: 15px; max-width: 15px;
max-height: 15px; max-height: 15px;
height: 15px; height: 15px;
width: 15px; width: 15px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-video-radius);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
@ -38,7 +38,7 @@
margin: auto; margin: auto;
height: 80%; height: 80%;
width: 3px; width: 3px;
background: var(--ov-secondary-action-color); background: var(--ov-text-color);
border-radius: 8px; border-radius: 8px;
} }

View File

@ -10,7 +10,6 @@ import { Component } from '@angular/core';
<div class="stick loud play"></div> <div class="stick loud play"></div>
<div class="stick normal play"></div> <div class="stick normal play"></div>
</div>`, </div>`,
styleUrls: ['./audio-wave.component.scss'], styleUrls: ['./audio-wave.component.scss']
standalone: false
}) })
export class AudioWaveComponent {} export class AudioWaveComponent {}

View File

@ -19,7 +19,7 @@
height: 70px; height: 70px;
width: 70px; width: 70px;
border-radius: var(--ov-video-radius); border-radius: var(--ov-video-radius);
border: 2px solid var(--ov-text-primary-color); border: 2px solid var(--ov-text-color);
color: #000000; color: #000000;
} }

View File

@ -13,8 +13,7 @@ import { Component, Input } from '@angular/core';
</div> </div>
</div> </div>
`, `,
styleUrls: ['./avatar-profile.component.scss'], styleUrls: ['./avatar-profile.component.scss']
standalone: false
}) })
export class AvatarProfileComponent { export class AvatarProfileComponent {
letter: string; letter: string;

View File

@ -29,7 +29,7 @@
.error-container { .error-container {
display: grid; display: grid;
text-align: center; text-align: center;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
font-size: 18px; font-size: 18px;
} }
@ -43,7 +43,7 @@ mat-spinner {
} }
::ng-deep .mat-mdc-progress-spinner { ::ng-deep .mat-mdc-progress-spinner {
--mdc-circular-progress-active-indicator-color: var(--ov-accent-action-color); --mdc-circular-progress-active-indicator-color: var(--ov-tertiary-color);
} }
/* /*
@ -160,8 +160,8 @@ mat-spinner {
} }
#caption-settings-btn { #caption-settings-btn {
color: var(--ov-text-primary-color); color: var(--ov-text-color);
background-color: var(--ov-secondary-action-color); background-color: var(--ov-secondary-color);
} }
#caption-settings-icon { #caption-settings-icon {
font-size: 15px; font-size: 15px;
@ -190,12 +190,12 @@ mat-spinner {
.caption-text, .caption-text,
#speaker { #speaker {
color: var(--ov-text-primary-color); color: var(--ov-text-color);
font-family: 'Roboto', arial, sans-serif; font-family: 'Roboto', arial, sans-serif;
} }
.caption-text { .caption-text {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-logo-background-color);
padding: 4.5px; padding: 4.5px;
line-height: 1.6; line-height: 1.6;
word-break: break-word; word-break: break-word;

View File

@ -5,39 +5,15 @@ import { MatDialogRef } from '@angular/material/dialog';
* @internal * @internal
*/ */
@Component({ @Component({
selector: 'app-delete-dialog', selector: 'app-delete-dialog',
template: ` template: `
<div mat-dialog-content>{{ 'PANEL.RECORDING.DELETE_QUESTION' | translate }}</div> <div mat-dialog-content>{{'PANEL.RECORDING.DELETE_QUESTION' | translate}}</div>
<div mat-dialog-actions> <div mat-dialog-actions>
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.RECORDING.CANCEL' | translate }}</button> <button mat-button (click)="close()">{{'PANEL.RECORDING.CANCEL' | translate }}</button>
<button [disableRipple]="true" mat-button cdkFocusInitial (click)="close(true)" id="delete-recording-confirm-btn"> <button mat-button cdkFocusInitial (click)="close(true)" id="delete-recording-confirm-btn">{{'PANEL.RECORDING.DELETE' | translate}}</button>
{{ 'PANEL.RECORDING.DELETE' | translate }}
</button>
</div> </div>
`, `,
styles: [ styles: [``]
`
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
#delete-recording-confirm-btn {
background-color: var(--ov-error-color) !important;
color: var(--ov-secondary-action-color);
}
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
`
],
standalone: false
}) })
export class DeleteDialogComponent { export class DeleteDialogComponent {
constructor(public dialogRef: MatDialogRef<DeleteDialogComponent>) {} constructor(public dialogRef: MatDialogRef<DeleteDialogComponent>) {}

View File

@ -7,40 +7,17 @@ import { DialogData } from '../../models/dialog.model';
*/ */
@Component({ @Component({
selector: 'ov-dialog-template', selector: 'ov-dialog-template',
template: ` template: `
<h1 mat-dialog-title>{{ data.title }}</h1> <h1 mat-dialog-title>{{ data.title }}</h1>
<div mat-dialog-content id="openvidu-dialog">{{ data.description }}</div> <div mat-dialog-content id="openvidu-dialog">{{ data.description }}</div>
<div mat-dialog-actions *ngIf="data.showActionButtons"> <div mat-dialog-actions *ngIf="data.showActionButtons">
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button> <button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button>
</div> </div>
`, `
styles: [
`
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
`
],
standalone: false
}) })
export class DialogTemplateComponent { export class DialogTemplateComponent {
constructor( constructor(public dialogRef: MatDialogRef<DialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
public dialogRef: MatDialogRef<DialogTemplateComponent>,
@Inject(MAT_DIALOG_DATA) public data: DialogData
) {}
close() { close() {
this.dialogRef.close(); this.dialogRef.close();

View File

@ -8,8 +8,8 @@ import { DialogData } from '../../models/dialog.model';
*/ */
@Component({ @Component({
selector: 'ov-pro-feature-template', selector: 'ov-pro-feature-template',
template: ` template: `
<h1 mat-dialog-title>{{ data.title }}</h1> <h1 mat-dialog-title>{{ data.title }}</h1>
<div mat-dialog-content>{{ data.description }}</div> <div mat-dialog-content>{{ data.description }}</div>
<div mat-dialog-actions *ngIf="data.showActionButtons"> <div mat-dialog-actions *ngIf="data.showActionButtons">
@ -19,8 +19,7 @@ import { DialogData } from '../../models/dialog.model';
</button> </button>
<button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button> <button mat-button (click)="close()">{{'PANEL.CLOSE' | translate}}</button>
</div> </div>
`, `
standalone: false
}) })
export class ProFeatureDialogTemplateComponent { export class ProFeatureDialogTemplateComponent {
constructor(public dialogRef: MatDialogRef<ProFeatureDialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {} constructor(public dialogRef: MatDialogRef<ProFeatureDialogTemplateComponent>, @Inject(MAT_DIALOG_DATA) public data: DialogData) {}
@ -30,6 +29,6 @@ export class ProFeatureDialogTemplateComponent {
} }
seeMore() { seeMore() {
window.open('https://openvidu.io/pricing/#openvidu-pro', '_blank')?.focus(); window.open('https://docs.openvidu.io/en/stable/openvidu-pro/', '_blank')?.focus();
} }
} }

View File

@ -6,40 +6,23 @@ import { RecordingDialogData } from '../../models/dialog.model';
* @internal * @internal
*/ */
@Component({ @Component({
selector: 'app-recording-dialog', selector: 'app-recording-dialog',
template: ` template: `
<div mat-dialog-content> <div mat-dialog-content>
<video #videoElement controls autoplay [src]="src" (error)="handleError()"></video> <video #videoElement controls autoplay [src]="src" (error)="handleError()"></video>
</div> </div>
<div mat-dialog-actions *ngIf="data.showActionButtons" align="end"> <div mat-dialog-actions *ngIf="data.showActionButtons" align="end">
<button mat-button [disableRipple]="true" (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button> <button mat-button (click)="close()">{{ 'PANEL.CLOSE' | translate }}</button>
</div> </div>
`, `,
styles: [ styles: [
` `
::ng-deep .mat-mdc-dialog-content {
color: var(--ov-text-surface-color) !important;
}
::ng-deep .mat-mdc-dialog-surface {
background-color: var(--ov-surface-color);
border-radius: var(--ov-surface-radius);
}
video { video {
max-height: 64vh; max-height: 64vh;
max-width: 100%; max-width: 100%;
} }
.mat-mdc-button,
.mat-mdc-button:not(:disabled),
::ng-deep .mat-mdc-button .mat-mdc-button-persistent-ripple::before {
color: var(--ov-secondary-action-color);
background-color: var(--ov-primary-action-color) !important;
border-radius: var(--ov-surface-radius);
}
` `
], ]
standalone: false
}) })
export class RecordingDialogComponent { export class RecordingDialogComponent {
@ViewChild('videoElement', { static: true }) videoElement: ElementRef<HTMLVideoElement>; @ViewChild('videoElement', { static: true }) videoElement: ElementRef<HTMLVideoElement>;

View File

@ -19,11 +19,6 @@
<ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container> <ng-container *ngTemplateOutlet="streamTemplate; context: { $implicit: track }"></ng-container>
</div> </div>
<!-- Render additional layout elements injected via ovAdditionalLayoutElement -->
@if (layoutAdditionalElementsTemplate) {
<ng-container *ngTemplateOutlet="layoutAdditionalElementsTemplate"></ng-container>
}
<div <div
*ngFor="let track of remoteParticipants | tracks; trackBy: trackParticipantElement" *ngFor="let track of remoteParticipants | tracks; trackBy: trackParticipantElement"
class="remote-participant" class="remote-participant"

View File

@ -21,6 +21,6 @@ It will recognise the following directive in a child element.
With the following directives you can modify the default User Interface with the aim of fully customizing your videoconference application. With the following directives you can modify the default User Interface with the aim of fully customizing your videoconference application.
<!-- start-dynamic-api-directives-content --> <!-- start-dynamic-api-directives-content -->
_No API directives available for this component_. _No API directives available for this component_.
<!-- end-dynamic-api-directives-content --> <!-- end-dynamic-api-directives-content -->

View File

@ -31,7 +31,7 @@
.OV_root, .OV_root,
.OV_root * { .OV_root * {
color: var(--ov-secondary-action-color); color: #ffffff;
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;

View File

@ -1,5 +1,3 @@
import { LayoutAdditionalElementsDirective } from '../../directives/template/internals.directive';
import { import {
AfterViewInit, AfterViewInit,
ChangeDetectionStrategy, ChangeDetectionStrategy,
@ -13,7 +11,7 @@ import {
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } 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 { StreamDirective } from '../../directives/template/openvidu-components-angular.directive';
import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model'; import { ParticipantTrackPublication, ParticipantModel } from '../../models/participant.model';
import { LayoutService } from '../../services/layout/layout.service'; 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 { CdkDrag } from '@angular/cdk/drag-drop';
import { PanelService } from '../../services/panel/panel.service'; import { PanelService } from '../../services/panel/panel.service';
import { GlobalConfigService } from '../../services/config/global-config.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 { 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', selector: 'ov-layout',
templateUrl: './layout.component.html', templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'], styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit { 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>; @ContentChild('stream', { read: TemplateRef }) streamTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('layoutAdditionalElements', { read: TemplateRef }) layoutAdditionalElementsTemplate: TemplateRef<any>;
/** /**
* @ignore * @ignore
*/ */
@ -70,27 +62,9 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
// is inside of the layout component tagged with '*ovLayout' directive // is inside of the layout component tagged with '*ovLayout' directive
if (externalStream) { if (externalStream) {
this.streamTemplate = externalStream.template; 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; localParticipant: ParticipantModel | undefined;
remoteParticipants: ParticipantModel[] = []; remoteParticipants: ParticipantModel[] = [];
/** /**
@ -98,31 +72,31 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
captionsEnabled = true; captionsEnabled = true;
private _externalStream?: StreamDirective; private localParticipantSubs: Subscription;
private _externalLayoutAdditionalElements?: LayoutAdditionalElementsDirective; private remoteParticipantsSubs: Subscription;
private captionsSubs: Subscription;
private destroy$ = new Subject<void>();
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
private cdkSubscription: Subscription;
private resizeTimeout: NodeJS.Timeout; private resizeTimeout: NodeJS.Timeout;
private videoIsAtRight: boolean = false; private videoIsAtRight: boolean = false;
private lastLayoutWidth: number = 0; private lastLayoutWidth: number = 0;
private layoutService: LayoutService;
/** /**
* @ignore * @ignore
*/ */
constructor( constructor(
private layoutService: LayoutService, private serviceConfig: ServiceConfigService,
private panelService: PanelService, private panelService: PanelService,
private participantService: ParticipantService, private participantService: ParticipantService,
private globalService: GlobalConfigService, private globalService: GlobalConfigService,
private directiveService: OpenViduComponentsConfigService, private directiveService: OpenViduComponentsConfigService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef
private templateManagerService: TemplateManagerService ) {
) {} this.layoutService = this.serviceConfig.getLayoutService();
}
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.subscribeToParticipants(); this.subscribeToParticipants();
this.subscribeToCaptions(); this.subscribeToCaptions();
} }
@ -136,11 +110,13 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
} }
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.localParticipant = undefined; this.localParticipant = undefined;
this.remoteParticipants = []; this.remoteParticipants = [];
this.resizeObserver?.disconnect(); this.resizeObserver?.disconnect();
this.localParticipantSubs?.unsubscribe();
this.remoteParticipantsSubs?.unsubscribe();
this.captionsSubs?.unsubscribe();
this.cdkSubscription?.unsubscribe();
this.layoutService.clear(); this.layoutService.clear();
} }
@ -153,36 +129,8 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
return track; 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() { private subscribeToCaptions() {
this.layoutService.captionsTogglingObs.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => { this.captionsSubs = this.layoutService.captionsTogglingObs.subscribe((value: boolean) => {
this.captionsEnabled = value; this.captionsEnabled = value;
this.cd.markForCheck(); this.cd.markForCheck();
this.layoutService.update(); this.layoutService.update();
@ -190,7 +138,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
} }
private subscribeToParticipants() { private subscribeToParticipants() {
this.participantService.localParticipant$.pipe(takeUntil(this.destroy$)).subscribe((p) => { this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p) => {
if (p) { if (p) {
this.localParticipant = p; this.localParticipant = p;
if (!this.localParticipant?.isMinimized) { if (!this.localParticipant?.isMinimized) {
@ -201,18 +149,20 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
} }
}); });
combineLatest([this.participantService.remoteParticipants$, this.directiveService.layoutRemoteParticipants$]) this.remoteParticipantsSubs = combineLatest([
.pipe( this.participantService.remoteParticipants$,
map(([serviceParticipants, directiveParticipants]) => this.directiveService.layoutRemoteParticipants$
directiveParticipants !== undefined ? directiveParticipants : serviceParticipants ])
), .pipe(
takeUntil(this.destroy$) map(([serviceParticipants, directiveParticipants]) =>
directiveParticipants !== undefined ? directiveParticipants : serviceParticipants
) )
.subscribe((participants) => { )
this.remoteParticipants = participants; .subscribe((participants) => {
this.layoutService.update(); this.remoteParticipants = participants;
this.cd.markForCheck(); this.layoutService.update();
}); this.cd.markForCheck();
});
} }
private listenToResizeLayout() { private listenToResizeLayout() {
@ -271,8 +221,7 @@ export class LayoutComponent implements OnInit, OnDestroy, AfterViewInit {
this.videoIsAtRight = false; this.videoIsAtRight = false;
} }
}; };
this.cdkSubscription = this.cdkDrag.released.subscribe(handler);
this.cdkDrag.released.pipe(takeUntil(this.destroy$)).subscribe(handler);
if (this.globalService.isProduction()) return; if (this.globalService.isProduction()) return;
// Just for allow E2E testing with drag and drop // Just for allow E2E testing with drag and drop

View File

@ -3,7 +3,7 @@ video {
object-fit: cover; object-fit: cover;
width: 100%; width: 100%;
height: 100%; height: 100%;
color: var(--ov-text-primary-color); color: #ffffff;
margin: 0; margin: 0;
padding: 0; padding: 0;
border: 0; border: 0;

View File

@ -18,8 +18,7 @@ import { Track } from 'livekit-client';
transition(':enter', [style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]), transition(':enter', [style({ opacity: 0 }), animate('100ms', style({ opacity: 1 }))]),
transition(':leave', [style({ opacity: 1 }), animate('200ms', style({ opacity: 0 }))]) transition(':leave', [style({ opacity: 1 }), animate('200ms', style({ opacity: 0 }))])
]) ])
], ]
standalone: false
}) })
export class MediaElementComponent implements AfterViewInit { export class MediaElementComponent implements AfterViewInit {
_track: Track; _track: Track;

View File

@ -17,8 +17,6 @@
(onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)" (onRecordingDeleteRequested)="onRecordingDeleteRequested.emit($event)"
(onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)" (onRecordingDownloadClicked)="onRecordingDownloadClicked.emit($event)"
(onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)" (onRecordingPlayClicked)="onRecordingPlayClicked.emit($event)"
(onViewRecordingClicked)="onViewRecordingClicked.emit($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked.emit()"
></ov-recording-activity> ></ov-recording-activity>
<ov-broadcasting-activity <ov-broadcasting-activity
*ngIf="showBroadcastingActivity" *ngIf="showBroadcastingActivity"

View File

@ -4,6 +4,6 @@ With the following directives you can modify the default User Interface with the
<!-- start-dynamic-api-directives-content --> <!-- start-dynamic-api-directives-content -->
| **Parameter** | **Type** | **Reference** | | **Parameter** | **Type** | **Reference** |
|:--------------------------------: | :-------: | :---------------------------------------------: | |:--------------------------------: | :-------: | :---------------------------------------------: |
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
| **recordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) | | **recordingActivity** | `boolean` | [ActivitiesPanelRecordingActivityDirective](../directives/ActivitiesPanelRecordingActivityDirective.html) |
| **broadcastingActivity** | `boolean` | [ActivitiesPanelBroadcastingActivityDirective](../directives/ActivitiesPanelBroadcastingActivityDirective.html) |
<!-- end-dynamic-api-directives-content --> <!-- end-dynamic-api-directives-content -->

View File

@ -1,6 +1,4 @@
$ov-activity-status-color: #afafaf; :host{
:host {
.activities-body-container { .activities-body-container {
display: block !important; display: block !important;
overflow-y: auto; overflow-y: auto;
@ -8,26 +6,16 @@ $ov-activity-status-color: #afafaf;
padding: 10px; padding: 10px;
} }
.activity-status {
color: var(--ov-text-surface-color);
display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-surface-radius);
background-color: $ov-activity-status-color;
}
.activity-icon { .activity-icon {
display: inherit; display: inherit;
background-color: $ov-activity-status-color;; background-color: var(--ov-light-color);
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
margin: 10px 0px !important; margin: 10px 0px !important;
padding: 10px; padding: 10px;
} }
.activity-title, .activity-title, .activity-subtitle {
.activity-subtitle { color: var(--ov-panel-text-color);
color: var(--ov-text-surface-color);
} }
.activity-subtitle { .activity-subtitle {
@ -58,11 +46,6 @@ $ov-activity-status-color: #afafaf;
::ng-deep .mat-expansion-panel-header { ::ng-deep .mat-expansion-panel-header {
padding: 0px 5px !important; padding: 0px 5px !important;
height: 65px !important; height: 65px !important;
cursor: pointer !important;
}
::ng-deep .mat-expansion-panel-header:hover {
background: none !important;
} }
::ng-deep .mat-mdc-list-base .mat-mdc-list-item .mat-list-item-content, ::ng-deep .mat-mdc-list-base .mat-mdc-list-item .mat-list-item-content,
@ -70,10 +53,6 @@ $ov-activity-status-color: #afafaf;
padding: 0px !important; padding: 0px !important;
} }
::ng-deep mat-list mat-list-item {
cursor: pointer !important;
}
::ng-deep mat-expansion-panel .mat-expansion-panel-body { ::ng-deep mat-expansion-panel .mat-expansion-panel-body {
padding: 0px !important; padding: 0px !important;
min-height: 400px; min-height: 400px;
@ -84,16 +63,14 @@ $ov-activity-status-color: #afafaf;
::ng-deep .mat-expansion-panel { ::ng-deep .mat-expansion-panel {
box-shadow: none !important; box-shadow: none !important;
background-color: var(--ov-surface-color) !important; background-color: var(--ov-panel-background) !important;
} }
::ng-deep .no-body .mat-expansion-panel-content { ::ng-deep .no-body .mat-expansion-panel-content {
display: none !important; display: none !important;
} }
::ng-deep ::ng-deep .mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta .mdc-list-item__end::before{
.mdc-list-item--with-leading-icon.mdc-list-item--with-two-lines.mdc-list-item--with-trailing-meta
.mdc-list-item__end::before {
max-height: 24px; max-height: 24px;
height: 24px; height: 24px;
} }

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, OnInit, Output } from '@angular/core'; 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 { PanelStatusInfo, PanelType } from '../../../models/panel.model';
import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service';
import { PanelService } from '../../../services/panel/panel.service'; import { PanelService } from '../../../services/panel/panel.service';
@ -20,8 +20,7 @@ import { BroadcastingStartRequestedEvent, BroadcastingStopRequestedEvent } from
selector: 'ov-activities-panel', selector: 'ov-activities-panel',
templateUrl: './activities-panel.component.html', templateUrl: './activities-panel.component.html',
styleUrls: ['../panel.component.scss', './activities-panel.component.scss'], styleUrls: ['../panel.component.scss', './activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class ActivitiesPanelComponent implements OnInit { export class ActivitiesPanelComponent implements OnInit {
/** /**
@ -54,21 +53,6 @@ export class ActivitiesPanelComponent implements OnInit {
*/ */
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>(); @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. * Provides event notifications that fire when start broadcasting button is clicked.
* It provides the {@link BroadcastingStartRequestedEvent} payload as event data. * It provides the {@link BroadcastingStartRequestedEvent} payload as event data.
@ -95,7 +79,9 @@ export class ActivitiesPanelComponent implements OnInit {
* @internal * @internal
*/ */
showBroadcastingActivity: boolean = true; showBroadcastingActivity: boolean = true;
private destroy$ = new Subject<void>(); private panelSubscription: Subscription;
private recordingActivitySub: Subscription;
private broadcastingActivitySub: Subscription;
/** /**
* @internal * @internal
@ -118,8 +104,9 @@ export class ActivitiesPanelComponent implements OnInit {
* @internal * @internal
*/ */
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); if (this.panelSubscription) this.panelSubscription.unsubscribe();
this.destroy$.complete(); if (this.recordingActivitySub) this.recordingActivitySub.unsubscribe();
if (this.broadcastingActivitySub) this.broadcastingActivitySub.unsubscribe();
} }
/** /**
@ -130,7 +117,7 @@ export class ActivitiesPanelComponent implements OnInit {
} }
private subscribeToPanelToggling() { 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) { if (ev.panelType === PanelType.ACTIVITIES && !!ev.subOptionType) {
this.expandedPanel = ev.subOptionType; this.expandedPanel = ev.subOptionType;
} }
@ -138,12 +125,12 @@ export class ActivitiesPanelComponent implements OnInit {
} }
private subscribeToActivitiesPanelDirective() { private subscribeToActivitiesPanelDirective() {
this.libService.recordingActivity$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => { this.recordingActivitySub = this.libService.recordingActivity$.subscribe((value: boolean) => {
this.showRecordingActivity = value; this.showRecordingActivity = value;
this.cd.markForCheck(); 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.showBroadcastingActivity = value;
this.cd.markForCheck(); this.cd.markForCheck();
}); });

View File

@ -1,9 +1,4 @@
<mat-expansion-panel <mat-expansion-panel (opened)="setPanelOpened(true)" (closed)="setPanelOpened(false)" [expanded]="expanded" [ngClass]="{ 'no-body': !isPanelOpened }">
(opened)="setPanelOpened(true)"
(closed)="setPanelOpened(false)"
[expanded]="expanded"
[ngClass]="{ 'no-body': !isPanelOpened }"
>
<mat-expansion-panel-header> <mat-expansion-panel-header>
<mat-list> <mat-list>
<mat-list-item> <mat-list-item>
@ -34,7 +29,6 @@
<div class="activity-action-buttons" matListItemMeta> <div class="activity-action-buttons" matListItemMeta>
<div <div
id="broadcasting-status" id="broadcasting-status"
class="activity-status"
[ngClass]="{ [ngClass]="{
started: broadcastingStatus === broadcastingStatusEnum.STARTED, started: broadcastingStatus === broadcastingStatusEnum.STARTED,
stopped: broadcastingStatus === broadcastingStatusEnum.STOPPED, stopped: broadcastingStatus === broadcastingStatusEnum.STOPPED,

View File

@ -1,26 +1,29 @@
$ov-broadcasting-color: #5903ca; #broadcasting-status {
color: var(--ov-text-color);
$ov-input-color: #cccccc; display: inline;
padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
}
.time-container { .time-container {
padding: 2px; padding: 2px;
} }
.error-text { .error-text {
color: var(--ov-error-color); color: var(--ov-warn-color);
font-style: italic; font-style: italic;
font-size: 14px; font-size: 14px;
} }
#broadcasting-icon { #broadcasting-icon {
color: $ov-broadcasting-color !important; color: #5903ca;
} }
.broadcasting-duration { .broadcasting-duration {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-light-color);
padding: 4px 8px; padding: 4px 8px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
font-weight: 500; font-weight: 500;
} }
@ -31,27 +34,27 @@ $ov-input-color: #cccccc;
} }
.started { .started {
background-color: $ov-broadcasting-color !important; background-color: #5903ca !important;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.activity-icon.started { .activity-icon.started {
background-color: $ov-broadcasting-color !important; background-color: #5903ca !important;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.failed { .failed {
background-color: var(--ov-error-color) !important; background-color: var(--ov-warn-color) !important;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.stopped { .stopped {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-light-color);
color: var(--ov-text-surface-color) !important; color: var(--ov-panel-text-color) !important;
} }
.starting { .starting {
background-color: var(--ov-warn-color) !important; background-color: #ffd79b !important;
color: var(--ov-text-primary-color) !important; color: var(--ov-panel-text-color) !important;
} }
.panel-body-container { .panel-body-container {
@ -69,7 +72,7 @@ $ov-input-color: #cccccc;
} }
.broadcasting-error { .broadcasting-error {
color: var(--ov-error-color); color: var(--ov-warn-color);
font-weight: 600; font-weight: 600;
} }
.broadcasting-name { .broadcasting-name {
@ -89,13 +92,13 @@ $ov-input-color: #cccccc;
/* #start-broadcasting-btn { /* #start-broadcasting-btn {
width: 100%; width: 100%;
background-color: var(--ov-accent-action-color); background-color: var(--ov-tertiary-color);
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} */ } */
#stop-broadcasting-btn { #stop-broadcasting-btn {
/* background-color: var(--ov-error-color); */ /* background-color: var(--ov-warn-color); */
color: var(--ov-error-color); color: var(--ov-warn-color);
} }
#reset-broadcasting-status-btn { #reset-broadcasting-status-btn {
@ -119,13 +122,13 @@ mat-expansion-panel {
.input-container { .input-container {
height: 25px; height: 25px;
display: flex; display: flex;
background-color: $ov-input-color; background-color: var(--ov-light-color);
padding: 10px; padding: 10px;
margin: 10px; margin: 10px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
order: 3; order: 3;
justify-content: space-evenly; justify-content: space-evenly;
align-items: center; align-items: center;
} }
.input-container input { .input-container input {
@ -140,6 +143,7 @@ mat-expansion-panel {
white-space: pre-wrap; white-space: pre-wrap;
resize: none; resize: none;
outline: none; outline: none;
-webkit-box-shadow: none; -webkit-box-shadow: none;
-moz-box-shadow: none; -moz-box-shadow: none;
box-shadow: none; box-shadow: none;

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
BroadcastingStartRequestedEvent, BroadcastingStartRequestedEvent,
BroadcastingStatus, BroadcastingStatus,
@ -18,8 +18,7 @@ import { OpenViduService } from '../../../../services/openvidu/openvidu.service'
selector: 'ov-broadcasting-activity', selector: 'ov-broadcasting-activity',
templateUrl: './broadcasting-activity.component.html', templateUrl: './broadcasting-activity.component.html',
styleUrls: ['./broadcasting-activity.component.scss', '../activities-panel.component.scss'], styleUrls: ['./broadcasting-activity.component.scss', '../activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
// TODO: Allow to add more than one broadcast url // TODO: Allow to add more than one broadcast url
@ -76,7 +75,7 @@ export class BroadcastingActivityComponent implements OnInit {
*/ */
isPanelOpened: boolean = false; isPanelOpened: boolean = false;
private destroy$ = new Subject<void>(); private broadcastingSub: Subscription;
/** /**
* @internal * @internal
@ -99,8 +98,7 @@ export class BroadcastingActivityComponent implements OnInit {
* @internal * @internal
*/ */
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); if (this.broadcastingSub) this.broadcastingSub.unsubscribe();
this.destroy$.complete();
} }
/** /**
@ -148,7 +146,7 @@ export class BroadcastingActivityComponent implements OnInit {
} }
private subscribeToBroadcastingStatus() { private subscribeToBroadcastingStatus() {
this.broadcastingService.broadcastingStatusObs.pipe(takeUntil(this.destroy$)).subscribe((event: BroadcastingStatusInfo | undefined) => { this.broadcastingSub = this.broadcastingService.broadcastingStatusObs.subscribe((event: BroadcastingStatusInfo | undefined) => {
if (!!event) { if (!!event) {
const { status, broadcastingId, error } = event; const { status, broadcastingId, error } = event;
this.broadcastingStatus = status; this.broadcastingStatus = status;

View File

@ -1,8 +1,10 @@
:host { :host {
$ov-activity-status-color: #afafaf; #recording-status {
.recording-title, color: var(--ov-text-color);
.recording-subtitle { display: inline;
color: var(--ov-text-surface-color); padding: 3px;
font-size: 11px;
border-radius: var(--ov-panel-radius);
} }
.recording-title { .recording-title {
@ -21,15 +23,14 @@
flex-wrap: wrap; flex-wrap: wrap;
height: auto; height: auto;
align-content: center; align-content: center;
color: var(--ov-text-surface-color) !important; color: var(--ov-panel-text-color) !important;
} }
.recording-duration { .recording-duration {
background-color: $ov-activity-status-color; background-color: var(--ov-light-color);
padding: 4px 8px; padding: 4px 8px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
font-weight: 500; font-weight: 500;
color: var(--ov-text-surface-color);
} }
.recording-duration mat-icon { .recording-duration mat-icon {
@ -40,22 +41,22 @@
.started { .started {
background-color: #3b7430 !important; background-color: #3b7430 !important;
color: #ffffff !important; color: var(--ov-text-color);
} }
.activity-icon.started, .activity-icon.started,
.failed { .failed {
background-color: var(--ov-error-color) !important; background-color: var(--ov-warn-color) !important;
// color: var(--ov-secondary-action-color); color: var(--ov-text-color);
} }
.stopped { .stopped {
// background-color: var(--ov-secondary-action-color); background-color: var(--ov-light-color);
color: var(--ov-text-surface-color) !important; color: var(--ov-panel-text-color) !important;
} }
.starting { .starting {
background-color: var(--ov-warn-color) !important; background-color: #ffd79b !important;
color: #000000 !important; color: var(--ov-panel-text-color) !important;
} }
.panel-body-container { .panel-body-container {
@ -72,458 +73,18 @@
text-align: center; 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);
}
.recording-error { .recording-error {
color: var(--ov-error-color); color: var(--ov-warn-color);
font-weight: 600; font-weight: 600;
} }
.recording-name {
.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 {
font-size: 14px;
opacity: 0.85;
}
}
.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; font-size: 14px;
margin: 0; font-weight: bold;
opacity: 0.7; }
line-height: 1.4;
.recording-date {
font-size: 12px !important;
font-style: italic;
} }
.not-allowed-message { .not-allowed-message {
@ -532,44 +93,29 @@
} }
.recording-action-buttons { .recording-action-buttons {
margin: 5px 0px; margin-top: 20px;
margin-bottom: 20px;
} }
#start-recording-btn { #start-recording-btn {
width: 100%; width: 100%;
background-color: var(--ov-primary-action-color); background-color: var(--ov-tertiary-color);
color: var(--ov-secondary-action-color); color: var(--ov-text-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 { #stop-recording-btn {
width: 100%; width: 100%;
background-color: var(--ov-error-color); background-color: var(--ov-warn-color);
color: var(--ov-secondary-action-color); color: var(--ov-text-color);
border-radius: var(--ov-surface-radius); }
.delete-recording-btn {
color: var(--ov-warn-color);
} }
#reset-recording-status-btn { #reset-recording-status-btn {
width: 100%; width: 100%;
background-color: var(--ov-accent-action-color); background-color: var(--ov-light-color);
border-radius: var(--ov-surface-radius);
} }
.recording-item { .recording-item {
@ -587,7 +133,6 @@
width: 40px !important; width: 40px !important;
height: 40px !important; height: 40px !important;
padding: 5px !important; padding: 5px !important;
color: var(--ov-text-surface-color);
} }
#play-recording-btn > .mat-icon, #play-recording-btn > .mat-icon,
#download-recording-btn > .mat-icon, #download-recording-btn > .mat-icon,
@ -595,13 +140,6 @@
height: 20px !important; height: 20px !important;
} }
#delete-recording-btn {
color: var(--ov-error-color);
}
#download-recording-btn {
color: var(--ov-accent-action-color);
}
mat-expansion-panel { mat-expansion-panel {
margin: 0px 0px 5px 0px; margin: 0px 0px 5px 0px;
} }
@ -618,18 +156,6 @@
height: 0px !important; height: 0px !important;
} }
::ng-deep .mdc-list-item__secondary-text,
::ng-deep .mdc-list-item__primary-text {
color: var(--ov-text-surface-color);
}
// ::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;
}
.blink { .blink {
animation: blinker 1.5s linear infinite !important; animation: blinker 1.5s linear infinite !important;
} }

View File

@ -1,5 +1,5 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, OnDestroy, Output } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Subject, takeUntil } from 'rxjs'; import { Subscription } from 'rxjs';
import { import {
RecordingDeleteRequestedEvent, RecordingDeleteRequestedEvent,
RecordingDownloadClickedEvent, RecordingDownloadClickedEvent,
@ -16,7 +16,6 @@ import { RecordingService } from '../../../../services/recording/recording.servi
import { OpenViduService } from '../../../../services/openvidu/openvidu.service'; import { OpenViduService } from '../../../../services/openvidu/openvidu.service';
import { ILogger } from '../../../../models/logger.model'; import { ILogger } from '../../../../models/logger.model';
import { LoggerService } from '../../../../services/logger/logger.service'; 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. * 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', selector: 'ov-recording-activity',
templateUrl: './recording-activity.component.html', templateUrl: './recording-activity.component.html',
styleUrls: ['./recording-activity.component.scss', '../activities-panel.component.scss'], styleUrls: ['./recording-activity.component.scss', '../activities-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
// TODO: Allow to add more than one recording type // 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 where the recording is stored (s3, google cloud, etc)
// TODO: Allow to choose the layout of the recording // TODO: Allow to choose the layout of the recording
export class RecordingActivityComponent implements OnInit, OnDestroy { export class RecordingActivityComponent implements OnInit {
/** /**
* @internal * @internal
*/ */
@ -68,20 +66,6 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/ */
@Output() onRecordingPlayClicked: EventEmitter<RecordingPlayClickedEvent> = new EventEmitter<RecordingPlayClickedEvent>(); @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 * @internal
*/ */
@ -114,53 +98,12 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/ */
recordingError: any; recordingError: any;
/**
* @internal
*/
hasRoomTracksPublished: boolean = false;
/** /**
* @internal * @internal
*/ */
mouseHovering: boolean = false; 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 log: ILogger;
private destroy$ = new Subject<void>(); private recordingStatusSubscription: Subscription;
/** /**
* @internal * @internal
@ -171,8 +114,7 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
private actionService: ActionService, private actionService: ActionService,
private openviduService: OpenViduService, private openviduService: OpenViduService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef,
private loggerSrv: LoggerService, private loggerSrv: LoggerService
private libService: OpenViduComponentsConfigService
) { ) {
this.log = this.loggerSrv.get('RecordingActivityComponent'); this.log = this.loggerSrv.get('RecordingActivityComponent');
} }
@ -182,23 +124,13 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
*/ */
ngOnInit(): void { ngOnInit(): void {
this.subscribeToRecordingStatus(); this.subscribeToRecordingStatus();
this.subscribeToTracksChanges();
this.subscribeToConfigChanges();
} }
/** /**
* @internal * @internal
*/ */
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); if (this.recordingStatusSubscription) this.recordingStatusSubscription.unsubscribe();
this.destroy$.complete();
}
/**
* @internal
*/
trackByRecordingId(index: number, recording: RecordingInfo): string | undefined {
return recording.id;
} }
/** /**
@ -293,105 +225,11 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
this.recordingService.playRecording(recording); 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() { 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; const { status, recordingList, error } = event;
this.recordingStatus = status; this.recordingStatus = status;
if (this.showRecordingList) { this.recordingList = recordingList;
this.recordingList = recordingList;
} else {
// Avoid showing recordings
this.recordingList = [];
}
this.recordingError = error; this.recordingError = error;
this.recordingAlive = this.recordingStatus === RecordingStatus.STARTED; this.recordingAlive = this.recordingStatus === RecordingStatus.STARTED;
if (this.recordingStatus !== RecordingStatus.FAILED) { if (this.recordingStatus !== RecordingStatus.FAILED) {
@ -400,24 +238,4 @@ export class RecordingActivityComponent implements OnInit, OnDestroy {
this.cd.markForCheck(); 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();
}
});
}
} }

View File

@ -8,7 +8,7 @@
<div class="effects-container" fxFlex="100%" fxLayoutAlign="space-evenly none"> <div class="effects-container" fxFlex="100%" fxLayoutAlign="space-evenly none">
<div> <div>
<h4 class="background-title">{{ 'PANEL.BACKGROUND.BLURRED_SECTION' | translate }}</h4> <h4>{{ 'PANEL.BACKGROUND.BLURRED_SECTION' | translate }}</h4>
<div> <div>
<button <button
*ngFor="let effect of noEffectAndBlurredBackground" *ngFor="let effect of noEffectAndBlurredBackground"
@ -29,7 +29,7 @@
</div> </div>
<hr /> <hr />
<div> <div>
<h4 class="background-title">{{ 'PANEL.BACKGROUND.IMAGES_SECTION' | translate }}</h4> <h4>{{ 'PANEL.BACKGROUND.IMAGES_SECTION' | translate }}</h4>
<div class="grid"> <div class="grid">
<div <div

View File

@ -1,6 +1,3 @@
.background-title {
color: var(--ov-text-surface-color);
}
.effects-container { .effects-container {
display: block !important; display: block !important;
overflow-y: auto; overflow-y: auto;
@ -10,9 +7,8 @@
.effect-button { .effect-button {
margin: 5px; margin: 5px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
background-color: var(--ov-secondary-action-color); background-color: var(--ov-light-color);
color: var(--ov-primary-action-color);
width: 60px; width: 60px;
height: 60px; height: 60px;
line-height: inherit; line-height: inherit;
@ -23,7 +19,7 @@
} }
.active-effect-btn { .active-effect-btn {
border: 2px solid var(--ov-accent-action-color); border: 2px solid var(--ov-tertiary-color);
} }
#hard_blur-btn .mat-icon { #hard_blur-btn .mat-icon {
@ -38,7 +34,7 @@
.grid img { .grid img {
max-width: 100%; max-width: 100%;
max-height: 100%; max-height: 100%;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
/* TODO(mdc-migration): The following rule targets internal classes of slider that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of slider that may no longer apply for the MDC version. */

View File

@ -12,8 +12,7 @@ import { VirtualBackgroundService } from '../../../services/virtual-background/v
selector: 'ov-background-effects-panel', selector: 'ov-background-effects-panel',
templateUrl: './background-effects-panel.component.html', templateUrl: './background-effects-panel.component.html',
styleUrls: ['../panel.component.scss', './background-effects-panel.component.scss'], styleUrls: ['../panel.component.scss', './background-effects-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class BackgroundEffectsPanelComponent implements OnInit { export class BackgroundEffectsPanelComponent implements OnInit {
backgroundSelectedId: string; backgroundSelectedId: string;
@ -57,6 +56,10 @@ export class BackgroundEffectsPanelComponent implements OnInit {
} }
async applyBackground(effect: BackgroundEffect) { async applyBackground(effect: BackgroundEffect) {
await this.backgroundService.applyBackground(effect); if (effect.type === EffectType.NONE) {
await this.backgroundService.removeBackground();
} else {
await this.backgroundService.applyBackground(effect);
}
} }
} }

View File

@ -1,7 +1,6 @@
$ov-selection-color: #d4d6d7;
.text-container { .text-container {
color: var(--ov-text-primary-color); background-color: var(--ov-light-color);
color: var(--ov-panel-text-color);
text-align: center; text-align: center;
font-size: 12px; font-size: 12px;
flex: inherit; flex: inherit;
@ -9,7 +8,6 @@ $ov-selection-color: #d4d6d7;
.text-info { .text-info {
margin: auto; margin: auto;
color: var(--ov-text-surface-color);
} }
.vertical-align { .vertical-align {
@ -29,15 +27,13 @@ $ov-selection-color: #d4d6d7;
.input-container { .input-container {
height: 65px; height: 65px;
display: flex; display: flex;
background-color: var(--ov-surface-color); background-color: var(--ov-light-color);
border: 1px solid $ov-selection-color;
padding: 10px 5px 10px 10px; padding: 10px 5px 10px 10px;
margin: 10px; margin: 10px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
order: 3; order: 3;
justify-content: space-evenly; justify-content: space-evenly;
align-items: none; align-items: none;
} }
.input-container textarea { .input-container textarea {
@ -58,7 +54,6 @@ $ov-selection-color: #d4d6d7;
-moz-box-shadow: none; -moz-box-shadow: none;
box-shadow: none; box-shadow: none;
font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif; font-family: 'Roboto', 'RobotoDraft', Helvetica, Arial, sans-serif;
color: var(--ov-text-surface-color);
} }
.message { .message {
@ -78,27 +73,25 @@ $ov-selection-color: #d4d6d7;
.participant-name-container p { .participant-name-container p {
font-size: 13px; font-size: 13px;
font-style: italic; font-weight: bold;
color: var(--ov-text-surface-color); color: var(--ov-panel-text-color);
padding: 5px;
} }
.chat-message { .chat-message {
position: relative; position: relative;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
padding: 8px; padding: 8px;
color: var(--ov-secondary-action-color); color: #000000;
width: auto; width: auto;
max-width: 95%; max-width: 95%;
font-size: 14px; font-size: 13px;
word-break: break-all; word-break: break-all;
background-color: var(--ov-primary-action-color);
} }
#send-btn { #send-btn {
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
color: var(--ov-secondary-action-color); color: var(--ov-light-color);
background-color: var(--ov-primary-action-color); background-color: var(--ov-tertiary-color);
align-self: center; align-self: center;
height: 75px; height: 75px;
} }
@ -126,5 +119,5 @@ $ov-selection-color: #d4d6d7;
} }
::ng-deep a:-webkit-any-link { ::ng-deep a:-webkit-any-link {
color: var(--ov-accent-action-color); color: var(--ov-tertiary-color);
} }

View File

@ -1,5 +1,5 @@
import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnInit, ViewChild } from '@angular/core'; 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 { ChatMessage } from '../../../models/chat.model';
import { PanelType } from '../../../models/panel.model'; import { PanelType } from '../../../models/panel.model';
import { ChatService } from '../../../services/chat/chat.service'; import { ChatService } from '../../../services/chat/chat.service';
@ -13,8 +13,7 @@ import { PanelService } from '../../../services/panel/panel.service';
selector: 'ov-chat-panel', selector: 'ov-chat-panel',
templateUrl: './chat-panel.component.html', templateUrl: './chat-panel.component.html',
styleUrls: ['../panel.component.scss', './chat-panel.component.scss'], styleUrls: ['../panel.component.scss', './chat-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class ChatPanelComponent implements OnInit, AfterViewInit { export class ChatPanelComponent implements OnInit, AfterViewInit {
/** /**
@ -34,7 +33,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
*/ */
messageList: ChatMessage[] = []; messageList: ChatMessage[] = [];
private destroy$ = new Subject<void>(); private chatMessageSubscription: Subscription;
/** /**
* @ignore * @ignore
@ -66,8 +65,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
* @ignore * @ignore
*/ */
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroy$.next(); if (this.chatMessageSubscription) this.chatMessageSubscription.unsubscribe();
this.destroy$.complete();
} }
/** /**
@ -110,7 +108,7 @@ export class ChatPanelComponent implements OnInit, AfterViewInit {
} }
private subscribeToMessages() { private subscribeToMessages() {
this.chatService.messagesObs.pipe(takeUntil(this.destroy$)).subscribe((messages: ChatMessage[]) => { this.chatMessageSubscription = this.chatService.messagesObs.subscribe((messages: ChatMessage[]) => {
this.messageList = messages; this.messageList = messages;
if (this.panelService.isChatPanelOpened()) { if (this.panelService.isChatPanelOpened()) {
this.scrollToBottom(); this.scrollToBottom();

View File

@ -1,7 +1,7 @@
.panel-container { .panel-container {
margin: 20px; margin: 20px;
background-color: var(--ov-surface-color); background-color: var(--ov-panel-background);
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
max-height: calc(100% - 40px); max-height: calc(100% - 40px);
min-height: calc(100% - 40px); min-height: calc(100% - 40px);
display: flex; display: flex;
@ -16,11 +16,6 @@
align-items: center; align-items: center;
} }
.panel-title,
.panel-close-button {
color: var(--ov-text-surface-color);
}
.panel-title { .panel-title {
margin-left: 5px; margin-left: 5px;
margin-top: auto; margin-top: auto;
@ -29,7 +24,7 @@
.panel-close-button { .panel-close-button {
margin-left: auto; margin-left: auto;
border-radius: 50%; border-radius: var(--ov-buttons-radius);
} }
::-webkit-scrollbar { ::-webkit-scrollbar {
@ -46,6 +41,6 @@
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
background: var(--ov-secondary-action-color); background: var(--ov-light-color);
border-radius: 4px; border-radius: 4px;
} }

View File

@ -8,7 +8,7 @@ import {
Output, Output,
TemplateRef TemplateRef
} from '@angular/core'; } from '@angular/core';
import { skip, Subject, takeUntil } from 'rxjs'; import { skip, Subscription } from 'rxjs';
import { import {
ActivitiesPanelDirective, ActivitiesPanelDirective,
AdditionalPanelsDirective, AdditionalPanelsDirective,
@ -25,7 +25,6 @@ import {
} from '../../models/panel.model'; } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service'; import { PanelService } from '../../services/panel/panel.service';
import { BackgroundEffect } from '../../models/background-effect.model'; 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', selector: 'ov-panel',
templateUrl: './panel.component.html', templateUrl: './panel.component.html',
styleUrls: ['./panel.component.scss'], styleUrls: ['./panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class PanelComponent implements OnInit { export class PanelComponent implements OnInit {
/** /**
@ -76,20 +74,42 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(ParticipantsPanelDirective) @ContentChild(ParticipantsPanelDirective)
set externalParticipantPanel(externalParticipantsPanel: 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) { 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 * @ignore
*/ */
@ContentChild(ActivitiesPanelDirective) @ContentChild(ActivitiesPanelDirective)
set externalActivitiesPanel(externalActivitiesPanel: 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) { if (externalActivitiesPanel) {
this.updateTemplatesAndMarkForCheck(); this.activitiesPanelTemplate = externalActivitiesPanel.template;
} }
} }
@ -98,9 +118,10 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(ChatPanelDirective) @ContentChild(ChatPanelDirective)
set externalChatPanel(externalChatPanel: 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) { if (externalChatPanel) {
this.updateTemplatesAndMarkForCheck(); this.chatPanelTemplate = externalChatPanel.template;
} }
} }
@ -109,9 +130,10 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(AdditionalPanelsDirective) @ContentChild(AdditionalPanelsDirective)
set externalAdditionalPanels(externalAdditionalPanels: 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) { if (externalAdditionalPanels) {
this.updateTemplatesAndMarkForCheck(); this.additionalPanelsTemplate = externalAdditionalPanels.template;
} }
} }
@ -172,20 +194,7 @@ export class PanelComponent implements OnInit {
* @internal * @internal
*/ */
isExternalPanelOpened: boolean; isExternalPanelOpened: boolean;
private panelSubscription: Subscription;
/**
* @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 panelEmitersHandler: Map< private panelEmitersHandler: Map<
PanelType, PanelType,
@ -197,78 +206,30 @@ export class PanelComponent implements OnInit {
*/ */
constructor( constructor(
private panelService: PanelService, private panelService: PanelService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef
private templateManagerService: TemplateManagerService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.subscribeToPanelToggling(); this.subscribeToPanelToggling();
this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged); this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged); this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged); this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.ACTIVITIES, this.onActivitiesPanelStatusChanged); 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 * @ignore
*/ */
ngOnDestroy() { ngOnDestroy() {
this.isChatPanelOpened = false; this.isChatPanelOpened = false;
this.isParticipantsPanelOpened = false; this.isParticipantsPanelOpened = false;
this.destroy$.next(); if (this.panelSubscription) this.panelSubscription.unsubscribe();
this.destroy$.complete();
} }
private subscribeToPanelToggling() { 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.isChatPanelOpened = ev.isOpened && ev.panelType === PanelType.CHAT;
this.isParticipantsPanelOpened = ev.isOpened && ev.panelType === PanelType.PARTICIPANTS; this.isParticipantsPanelOpened = ev.isOpened && ev.panelType === PanelType.PARTICIPANTS;
this.isBackgroundEffectsPanelOpened = ev.isOpened && ev.panelType === PanelType.BACKGROUND_EFFECTS; this.isBackgroundEffectsPanelOpened = ev.isOpened && ev.panelType === PanelType.BACKGROUND_EFFECTS;

View File

@ -1,71 +1,33 @@
<mat-list> <mat-list>
<mat-list-item> <mat-list-item>
<!-- Main participant container with improved structure --> <div matListItemIcon class="participant-avatar" [style.background-color]="_participant.colorProfile">
<div class="participant-container" [attr.data-participant-id]="_participant?.sid"> <mat-icon>person</mat-icon>
<!-- Avatar section with dynamic color --> </div>
<div <h3 matListItemTitle class="participant-name">{{ _participant.name }}
class="participant-avatar" <span *ngIf="_participant.isLocal"> ({{ 'PANEL.PARTICIPANTS.YOU' | translate }})</span>
[style.background-color]="_participant?.colorProfile" </h3>
[attr.aria-label]="'Avatar for ' + participantDisplayName" <p matListItemLine class="participant-subtitle">{{ _participant | tracksPublishedTypes }}</p>
<!-- <p matListItemLine>
<span class="participant-subtitle"></span>
</p> -->
<div class="participant-action-buttons" matListItemMeta>
<button
mat-icon-button
id="mute-btn"
*ngIf="!_participant.isLocal && showMuteButton"
[class.warn-btn]="_participant.isMutedForcibly"
(click)="toggleMuteForcibly()"
[disableRipple]="true"
> >
<mat-icon>person</mat-icon> <mat-icon *ngIf="!_participant.isMutedForcibly">volume_up</mat-icon>
</div> <mat-icon *ngIf="_participant.isMutedForcibly">volume_off</mat-icon>
</button>
<!-- Content section with name and status --> <!-- External item elements -->
<div class="participant-content"> <ng-container *ngIf="participantPanelItemElementsTemplate">
<div class="participant-name"> <ng-container *ngTemplateOutlet="participantPanelItemElementsTemplate"></ng-container>
{{ participantDisplayName }} </ng-container>
<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 -->
<button
mat-icon-button
id="mute-btn"
*ngIf="!isLocalParticipant && 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>
</button>
<!-- External item elements with improved structure -->
<div class="external-elements" *ngIf="hasExternalElements">
<ng-container *ngTemplateOutlet="participantPanelItemElementsTemplate"></ng-container>
</div>
</div>
</div> </div>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>

View File

@ -1,443 +1,59 @@
:host { :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 { .participant-avatar {
display: flex; display: inherit;
align-items: center; border-radius: var(--ov-panel-radius);
justify-content: center; margin: auto !important;
width: 40px; padding: 10px;
height: 40px; color: var(--ov-panel-text-color);
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;
}
} }
// 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 { .participant-subtitle {
font-style: normal; font-style: italic;
font-size: 12px !important; font-size: 11px !important;
font-weight: 400;
margin: 0; margin: 0;
color: var(--ov-text-secondary, #757575); }
line-height: 1.3; .participant-name {
display: flex; font-weight: bold !important;
align-items: center; color: var(--ov-panel-text-color);
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;
}
// 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 { .participant-action-buttons {
display: flex; display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
margin-left: auto;
} }
// Mute button styling ::ng-deep .participant-action-buttons > *:not(#mute-btn) {
#mute-btn { display: contents;
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);
}
&: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 ::ng-deep .participant-action-buttons > *:not(#mute-btn) > * {
.participant-badges { margin: auto;
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 .mat-mdc-list-item { ::ng-deep .mat-mdc-list-item {
height: auto !important; height: max-content !important;
padding: 0 !important; padding-bottom: 10px !important;
min-height: auto !important; }
border-radius: var(--ov-surface-radius, 8px);
mat-list {
padding: 3px;
} }
::ng-deep .mdc-list-item__content { ::ng-deep .mdc-list-item__content {
padding: 0 !important; padding-left: 10px !important;
align-self: stretch !important; align-self: center !important;
width: 100%;
} }
::ng-deep .mat-mdc-list-base { ::ng-deep .mat-mdc-list-base {
--mdc-list-list-item-hover-label-text-color: unset; --mdc-list-list-item-hover-label-text-color: unset;
--mdc-list-list-item-hover-leading-icon-color: unset; --mdc-list-list-item-hover-leading-icon-color: unset;
padding: 0;
} }
::ng-deep .mat-mdc-list-item:hover { #mute-btn {
background-color: transparent !important; border-radius: var(--ov-buttons-radius);
} }
// Animations .warn-btn {
@keyframes fadeIn { /* background-color: var(--ov-warn-color) !important; */
from { color: var(--ov-warn-color);
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;
}
}
.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);
}
} }
} }

View File

@ -1,23 +1,21 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Input, OnDestroy, OnInit, TemplateRef } from '@angular/core';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-components-angular.directive'; import { ParticipantPanelItemElementsDirective } from '../../../../directives/template/openvidu-components-angular.directive';
import { ParticipantPanelParticipantBadgeDirective } from '../../../../directives/template/internals.directive';
import { ParticipantModel } from '../../../../models/participant.model'; import { ParticipantModel } from '../../../../models/participant.model';
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
import { ParticipantService } from '../../../../services/participant/participant.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}. * The **ParticipantPanelItemComponent** is hosted inside of the {@link ParticipantsPanelComponent}.
* It displays participant information with enhanced UI/UX, including support for custom content * It is in charge of displaying the participants information inside of the ParticipansPanelComponent.
* injection through structural directives.
*/ */
@Component({ @Component({
selector: 'ov-participant-panel-item', selector: 'ov-participant-panel-item',
templateUrl: './participant-panel-item.component.html', templateUrl: './participant-panel-item.component.html',
styleUrls: ['./participant-panel-item.component.scss'], styleUrls: ['./participant-panel-item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class ParticipantPanelItemComponent implements OnInit, OnDestroy { export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
/** /**
@ -36,69 +34,40 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
*/ */
@ContentChild(ParticipantPanelItemElementsDirective) @ContentChild(ParticipantPanelItemElementsDirective)
set externalItemElements(externalItemElements: 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) { if (externalItemElements) {
this.updateTemplatesAndMarkForCheck(); this.participantPanelItemElementsTemplate = externalItemElements.template;
} }
} }
/**
* The participant to be displayed
* @ignore
*/
@Input()
set participant(participant: ParticipantModel) {
this._participant = participant;
}
/** /**
* @ignore * @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; _participant: ParticipantModel;
/**
* Whether to show the mute button for remote participants
*/
@Input()
muteButton: boolean = true;
/** /**
* @ignore * @ignore
*/ */
constructor( constructor(
private libService: OpenViduComponentsConfigService, private libService: OpenViduComponentsConfigService,
private participantService: ParticipantService, private participantService: ParticipantService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef
private templateManagerService: TemplateManagerService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.subscribeToParticipantPanelItemDirectives(); this.subscribeToParticipantPanelItemDirectives();
} }
@ -110,72 +79,14 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
} }
/** /**
* Toggles the mute state of a remote participant * @ignore
*/ */
toggleMuteForcibly() { toggleMuteForcibly() {
if (this._participant && !this._participant.isLocal) { if (this._participant) {
this.participantService.setRemoteMutedForcibly(this._participant.sid, !this._participant.isMutedForcibly); 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() { private subscribeToParticipantPanelItemDirectives() {
this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => { this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => {
this.showMuteButton = value; this.showMuteButton = value;

View File

@ -7,13 +7,14 @@
</div> </div>
<div class="scrollable"> <div class="scrollable">
<div class="local-participant-container" *ngIf="localParticipant"> <div class="local-participant-container" *ngIf="localParticipant">
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: localParticipant }"></ng-container> <ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: localParticipant }"></ng-container>
<mat-divider *ngIf="true"></mat-divider> <mat-divider *ngIf="true"></mat-divider>
</div> </div>
<ng-container *ngTemplateOutlet="participantPanelAfterLocalParticipantTemplate"></ng-container>
<div class="remote-participants-container" id="remote-participants-container" *ngIf="remoteParticipants.length > 0"> <div class="remote-participants-container" id="remote-participants-container" *ngIf="remoteParticipants.length > 0">
<div *ngFor="let participant of this.remoteParticipants" id="remote-participant-item"> <div *ngFor="let participant of this.remoteParticipants" id="remote-participant-item">
<ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: participant }"></ng-container> <ng-container *ngTemplateOutlet="participantPanelItemTemplate; context: { $implicit: participant }"></ng-container>
</div> </div>

View File

@ -8,3 +8,16 @@
max-height: calc(100% - 60px); max-height: calc(100% - 60px);
overflow: auto; overflow: auto;
} }
.message-container {
padding: 5px;
background-color: var(--ov-light-color);
color: var(--ov-panel-text-color);
text-align: center;
margin: 5px 5px;
font-size: 12px;
}
.message-container p {
margin: 0;
}

View File

@ -13,10 +13,8 @@ import {
import { ParticipantService } from '../../../../services/participant/participant.service'; import { ParticipantService } from '../../../../services/participant/participant.service';
import { PanelService } from '../../../../services/panel/panel.service'; import { PanelService } from '../../../../services/panel/panel.service';
import { ParticipantPanelItemDirective } from '../../../../directives/template/openvidu-components-angular.directive'; 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 { 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}. * The **ParticipantsPanelComponent** is hosted inside of the {@link PanelComponent}.
@ -27,8 +25,7 @@ import { OpenViduComponentsConfigService } from '../../../../services/config/dir
selector: 'ov-participants-panel', selector: 'ov-participants-panel',
templateUrl: './participants-panel.component.html', templateUrl: './participants-panel.component.html',
styleUrls: ['../../panel.component.scss', './participants-panel.component.scss'], styleUrls: ['../../panel.component.scss', './participants-panel.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewInit { 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>; @ContentChild('participantPanelItem', { read: TemplateRef }) participantPanelItemTemplate: TemplateRef<any>;
/**
* @ignore
*/
@ContentChild('participantPanelAfterLocalParticipant', { read: TemplateRef })
participantPanelAfterLocalParticipantTemplate: TemplateRef<any>;
/** /**
* @ignore * @ignore
*/ */
@ContentChild(ParticipantPanelItemDirective) @ContentChild(ParticipantPanelItemDirective)
set externalParticipantPanelItem(externalParticipantPanelItem: 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) { if (externalParticipantPanelItem) {
this.updateTemplatesAndMarkForCheck(); this.participantPanelItemTemplate = externalParticipantPanelItem.template;
} }
} }
/** private localParticipantSubs: Subscription;
* @internal private remoteParticipantsSubs: Subscription;
* Template configuration managed by the service
*/
templateConfig: ParticipantsPanelTemplateConfiguration = {};
// Store directive references for template setup
private _externalParticipantPanelItem?: ParticipantPanelItemDirective;
private destroy$ = new Subject<void>();
/** /**
* @ignore * @ignore
@ -84,26 +68,32 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
constructor( constructor(
private participantService: ParticipantService, private participantService: ParticipantService,
private panelService: PanelService, private panelService: PanelService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef
private templateManagerService: TemplateManagerService,
private libService: OpenViduComponentsConfigService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { 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 * @ignore
*/ */
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); if (this.localParticipantSubs) this.localParticipantSubs.unsubscribe();
this.destroy$.complete(); 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 * @ignore
*/ */

View File

@ -22,27 +22,25 @@
[value]="settingsOptions.GENERAL" [value]="settingsOptions.GENERAL"
> >
<mat-icon matListItemIcon>manage_accounts</mat-icon> <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>
<mat-list-option <mat-list-option
*ngIf="showCameraButton"
class="option" class="option"
id="video-opt" id="video-opt"
[selected]="selectedOption === settingsOptions.VIDEO" [selected]="selectedOption === settingsOptions.VIDEO"
[value]="settingsOptions.VIDEO" [value]="settingsOptions.VIDEO"
> >
<mat-icon matListItemIcon>videocam</mat-icon> <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>
<mat-list-option <mat-list-option
*ngIf="showMicrophoneButton"
class="option" class="option"
id="audio-opt" id="audio-opt"
[selected]="selectedOption === settingsOptions.AUDIO" [selected]="selectedOption === settingsOptions.AUDIO"
[value]="settingsOptions.AUDIO" [value]="settingsOptions.AUDIO"
> >
<mat-icon matListItemIcon>mic</mat-icon> <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>
<!-- <mat-list-option <!-- <mat-list-option
*ngIf="showCaptions" *ngIf="showCaptions"
@ -59,10 +57,10 @@
<div class="item-content"> <div class="item-content">
<div *ngIf="selectedOption === settingsOptions.GENERAL"> <div *ngIf="selectedOption === settingsOptions.GENERAL">
<mat-label class="input-label">{{ 'PREJOIN.NICKNAME' | translate }}</mat-label> <mat-label>{{ 'PREJOIN.NICKNAME' | translate }}</mat-label>
<ov-participant-name-input></ov-participant-name-input> <ov-participant-name-input></ov-participant-name-input>
<mat-list> <mat-list>
<mat-list-item class="lang-selector"> <mat-list-item>
<mat-icon matListItemIcon>translate</mat-icon> <mat-icon matListItemIcon>translate</mat-icon>
<div matListItemTitle>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}</div> <div matListItemTitle>{{ 'PANEL.SETTINGS.LANGUAGE' | translate }}</div>
<ov-lang-selector matListItemMeta (onLangChanged)="onLangChanged.emit($event)"></ov-lang-selector> <ov-lang-selector matListItemMeta (onLangChanged)="onLangChanged.emit($event)"></ov-lang-selector>
@ -70,12 +68,12 @@
</mat-list> </mat-list>
</div> </div>
<ov-video-devices-select <ov-video-devices-select
*ngIf="showCameraButton && selectedOption === settingsOptions.VIDEO" *ngIf="selectedOption === settingsOptions.VIDEO"
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)" (onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
(onVideoEnabledChanged)="onVideoEnabledChanged.emit($event)" (onVideoEnabledChanged)="onVideoEnabledChanged.emit($event)"
></ov-video-devices-select> ></ov-video-devices-select>
<ov-audio-devices-select <ov-audio-devices-select
*ngIf="showMicrophoneButton && selectedOption === settingsOptions.AUDIO" *ngIf="selectedOption === settingsOptions.AUDIO"
(onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)" (onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)"
(onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)" (onAudioEnabledChanged)="onAudioEnabledChanged.emit($event)"
></ov-audio-devices-select> ></ov-audio-devices-select>

View File

@ -11,7 +11,7 @@
.item-menu { .item-menu {
padding-right: 5px; padding-right: 5px;
border-right: 1px solid var(--ov-secondary-action-color); border-right: 1px solid var(--ov-secondary-color);
width: 170px; width: 170px;
} }
.item-menu.mobile { .item-menu.mobile {
@ -29,18 +29,17 @@
} }
mat-list-option[aria-selected='true'] { mat-list-option[aria-selected='true'] {
background: var(--ov-accent-action-color) !important; background: var(--ov-tertiary-color) !important;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
::ng-deep .mat-mdc-list-item-unscoped-content, ::ng-deep .mat-mdc-list-item-unscoped-content,
mat-icon { mat-icon {
color: var(--ov-secondary-action-color) !important; color: var(--ov-panel-background) !important;
} }
} }
mat-list-option[aria-selected='false'] { mat-list-option[aria-selected='false'] {
::ng-deep .mdc-list-item__primary-text,
mat-icon { mat-icon {
color: var(--ov-text-surface-color) !important; color: var(--ov-panel-text-color) !important;
} }
} }
@ -51,14 +50,4 @@
.mat-mdc-list-base { .mat-mdc-list-base {
--mdc-list-list-item-focus-state-layer-color: transparent !important; --mdc-list-list-item-focus-state-layer-color: transparent !important;
} }
::ng-deep .lang-selector .expand-more-icon,
::ng-deep .lang-selector mat-icon {
color: var(--ov-secondary-action-color) !important;
}
::ng-deep .lang-selector div,
.input-label {
color: var(--ov-text-surface-color) !important;
}
} }

View File

@ -1,5 +1,5 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core'; 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 { PanelStatusInfo, PanelSettingsOptions, PanelType } from '../../../models/panel.model';
import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../../services/config/directive-config.service';
import { PanelService } from '../../../services/panel/panel.service'; import { PanelService } from '../../../services/panel/panel.service';
@ -13,8 +13,7 @@ import { LangOption } from '../../../models/lang.model';
@Component({ @Component({
selector: 'ov-settings-panel', selector: 'ov-settings-panel',
templateUrl: './settings-panel.component.html', templateUrl: './settings-panel.component.html',
styleUrls: ['../panel.component.scss', './settings-panel.component.scss'], styleUrls: ['../panel.component.scss', './settings-panel.component.scss']
standalone: false
}) })
export class SettingsPanelComponent implements OnInit { export class SettingsPanelComponent implements OnInit {
@Output() onVideoEnabledChanged = new EventEmitter<boolean>(); @Output() onVideoEnabledChanged = new EventEmitter<boolean>();
@ -24,11 +23,10 @@ export class SettingsPanelComponent implements OnInit {
@Output() onLangChanged = new EventEmitter<LangOption>(); @Output() onLangChanged = new EventEmitter<LangOption>();
settingsOptions: typeof PanelSettingsOptions = PanelSettingsOptions; settingsOptions: typeof PanelSettingsOptions = PanelSettingsOptions;
selectedOption: PanelSettingsOptions = PanelSettingsOptions.GENERAL; selectedOption: PanelSettingsOptions = PanelSettingsOptions.GENERAL;
showCameraButton: boolean = true;
showMicrophoneButton: boolean = true;
showCaptions: boolean = true; showCaptions: boolean = true;
panelSubscription: Subscription;
isMobile: boolean = false; isMobile: boolean = false;
private destroy$ = new Subject<void>(); private captionsSubs: Subscription;
constructor( constructor(
private panelService: PanelService, private panelService: PanelService,
private platformService: PlatformService, private platformService: PlatformService,
@ -41,8 +39,7 @@ export class SettingsPanelComponent implements OnInit {
} }
ngOnDestroy() { ngOnDestroy() {
this.destroy$.next(); if (this.captionsSubs) this.captionsSubs.unsubscribe();
this.destroy$.complete();
} }
close() { close() {
@ -53,13 +50,13 @@ export class SettingsPanelComponent implements OnInit {
} }
private subscribeToDirectives() { private subscribeToDirectives() {
this.libService.cameraButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showCameraButton = value)); this.captionsSubs = this.libService.captionsButton$.subscribe((value: boolean) => {
this.libService.microphoneButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showMicrophoneButton = value)); this.showCaptions = value;
this.libService.captionsButton$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => (this.showCaptions = value)); });
} }
private subscribeToPanelToggling() { 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) { if (ev.panelType === PanelType.SETTINGS && !!ev.subOptionType) {
this.selectedOption = ev.subOptionType as PanelSettingsOptions; this.selectedOption = ev.subOptionType as PanelSettingsOptions;
} }

View File

@ -5,7 +5,8 @@
<span>{{ 'PREJOIN.PREPARING' | translate }}</span> <span>{{ 'PREJOIN.PREPARING' | translate }}</span>
</div> </div>
<div *ngIf="!isLoading" id="prejoin-card"> <div *ngIf="!isLoading" id="prejoin-frame">
<!-- <img *ngIf="!isMinimal && showLogo" id="branding-logo" src="assets/images/lodgo.png" ovLogo /> -->
<ov-lang-selector *ngIf="!isMinimal" [compact]="true" class="lang-btn" (onLangChanged)="onLangChanged.emit($event)"> <ov-lang-selector *ngIf="!isMinimal" [compact]="true" class="lang-btn" (onLangChanged)="onLangChanged.emit($event)">
</ov-lang-selector> </ov-lang-selector>
@ -24,7 +25,7 @@
<div class="media-controls-container"> <div class="media-controls-container">
<!-- Camera --> <!-- Camera -->
<div class="video-controls-container" *ngIf="showCameraButton"> <div class="video-controls-container">
<ov-video-devices-select <ov-video-devices-select
(onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)" (onVideoDeviceChanged)="onVideoDeviceChanged.emit($event)"
(onVideoEnabledChanged)="videoEnabledChanged($event)" (onVideoEnabledChanged)="videoEnabledChanged($event)"
@ -32,7 +33,7 @@
</div> </div>
<!-- Microphone --> <!-- Microphone -->
<div class="audio-controls-container" *ngIf="showMicrophoneButton"> <div class="audio-controls-container">
<ov-audio-devices-select <ov-audio-devices-select
(onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)" (onAudioDeviceChanged)="onAudioDeviceChanged.emit($event)"
(onAudioEnabledChanged)="audioEnabledChanged($event)" (onAudioEnabledChanged)="audioEnabledChanged($event)"
@ -40,7 +41,7 @@
></ov-audio-devices-select> ></ov-audio-devices-select>
</div> </div>
<div class="participant-name-container" *ngIf="showParticipantName"> <div class="participant-name-container">
<ov-participant-name-input <ov-participant-name-input
[isPrejoinPage]="true" [isPrejoinPage]="true"
[error]="!!_error" [error]="!!_error"
@ -54,7 +55,7 @@
</div> </div>
<div class="join-btn-container"> <div class="join-btn-container">
<button mat-flat-button (click)="join()" id="join-button"> <button mat-flat-button (click)="joinSession()" id="join-button">
{{ 'PREJOIN.JOIN' | translate }} {{ 'PREJOIN.JOIN' | translate }}
</button> </button>
</div> </div>

View File

@ -1,38 +1,44 @@
:host { :host {
.container { --ov-primary-color-lighter: color-mix(in srgb, var(--ov-primary-color), #fff 15%);
height: 100%;
background-color: var(--ov-background-color);
display: flex;
justify-content: center;
align-items: center;
}
#loading-container { #loading-container {
position: absolute; position: absolute;
top: 40%; top: 40%;
bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
text-align: center; margin: auto;
color: var(--ov-text-primary-color); text-align: -webkit-center;
.mat-mdc-progress-spinner { text-align: -moz-center;
margin: auto; color: #fff;
}
} }
#prejoin-card { .container {
height: 100%;
background-color: var(--ov-secondary-color);
display: flex; display: flex;
flex-direction: column; }
align-items: center;
justify-content: center; #features-container {
margin: auto; margin: auto;
border-radius: var(--ov-surface-radius); width: 70vh;
width: 90%; height: 70vh;
max-width: 370px; margin-right: 0;
// max-height: 650px; box-shadow: -6px 2px 20px 0px #0003;
height: min-content; background-color: var(--ov-accent-color);
padding: 55px 30px; }
background-color: var(--ov-surface-color);
box-shadow: 6px 4px 20px rgba(0, 0, 0, 0.3); #prejoin-frame {
display: grid;
align-content: center;
margin: auto;
// margin-left: 0px;
border-radius: var(--ov-panel-radius);
width: 70vh;
height: 85vh;
padding: 20px;
background-color: var(--ov-panel-background);
box-shadow: 6px 4px 20px 0px #0003;
position: relative; position: relative;
} }
@ -44,116 +50,150 @@
font-size: 14px !important; font-size: 14px !important;
} }
::ng-deep .lang-btn mat-icon { #branding-logo {
color: var(--ov-text-surface-color) !important; position: absolute;
top: 10px;
left: 10px;
max-width: 50px;
}
#branding-logo {
border-radius: var(--ov-panel-radius);
max-width: 35px;
max-height: 35px;
margin-right: 10px;
} }
.video-container { .video-container {
margin: auto; margin: auto;
height: 35vh; min-height: 45vh;
width: 100%; max-height: 45vh;
max-width: 100%; height: 45vh;
display: flex; max-width: 80%;
justify-content: center;
align-items: center;
} }
#video-poster { #video-poster {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
border-radius: var(--ov-surface-radius);
overflow: hidden;
} }
.media-panel {
background-color: var(--ov-light-color);
}
.media-controls-container { .media-controls-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
justify-content: space-between; justify-content: space-between;
width: 100%; max-width: 80%;
margin-top: 15px; margin: auto;
height: auto; height: 25vh;
} }
.participant-name-container { .participant-name-container {
display: block !important; display: block !important;
width: 100%; width: 100%;
margin: 10px 0; margin-bottom: 2%;
} }
.video-controls-container, .video-controls-container,
.audio-controls-container { .audio-controls-container {
width: calc(50% - 10px); width: calc(50% - 3px);
margin: 5px 0; margin-top: 10px;
margin-bottom: 10px;
} }
.join-btn-container { .join-btn-container {
width: 100%; width: 100%;
margin-top: 15px;
} }
#join-button { #join-button {
background-color: var(--ov-primary-action-color); background-color: var(--ov-tertiary-color);
color: var(--ov-secondary-action-color); color: var(--ov-text-color);
font-weight: bold; font-weight: bold;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-video-radius);
width: 100%; width: 100%;
height: 50px; height: 50px;
transition: background-color 0.3s;
} }
// #join-button:hover {
// background-color: lighten(var(--ov-primary-action-color), 10%);
// }
.error { .error {
font-size: 12px; font-size: 12px;
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
color: var(--ov-error-color); color: var(--ov-warn-color);
margin-top: 5px;
} }
/* Styles for screens up to 768px wide */
@media (max-width: 768px) { @media (max-width: 768px) {
#prejoin-card { /* Specific styles for small screens */
padding: 10px; .container {
padding: 0px;
}
#prejoin-frame {
margin: auto;
height: 100%;
padding: 0px;
} }
.video-container { .video-container {
height: 40vh; height: 50vh;
width: 90%;
max-width: 90%;
} }
.media-controls-container { .media-controls-container {
flex-direction: column; height: 30vh;
align-items: center; width: 90%;
height: auto; max-width: 90%;
}
.video-controls-container,
.audio-controls-container {
width: 100%;
} }
} }
@media (max-width: 800px) and (orientation: landscape) { /* Styles for screens from 768px to 992px wide */
.media-controls-container { @media (min-width: 768px) and (max-width: 992px) {
flex-direction: row; /* Specific styles for medium screens */
justify-content: space-between; }
/* Styles for screens from 992px to 1200px wide */
@media (min-width: 992px) and (max-width: 1200px) {
/* Specific styles for large screens */
}
/* Styles for screens over 1200px wide */
@media (min-width: 1200px) {
/* Specific styles for extra-large screens */
}
/* Styles for screens with vertical orientation */
@media (orientation: portrait) {
/* Specific styles for screens in portrait orientation */
}
/* Styles for screens with horizontal orientation */
@media (max-width: 800) and (orientation: landscape) {
/* Specific styles for screens in landscape orientation */
.container {
height: 100%;
padding: 10px 60px;
}
.prejoin-toolbar {
display: none;
} }
.video-controls-container, .video-controls-container,
.audio-controls-container { .audio-controls-container {
width: 48%; width: 48%;
margin-bottom: 2%;
} }
} }
/* Styles for screens with maximum height of 630px */
@media (max-height: 630px) { @media (max-height: 630px) {
.video-container { .video-container {
height: 30vh; max-width: 85%;
height: 37vh;
min-height: 37vh;
} }
.media-controls-container { .media-controls-container {
height: auto; height: 35vh;
max-width: 85%;
} }
} }
} }

View File

@ -1,15 +1,5 @@
import { import { ChangeDetectorRef, Component, EventEmitter, HostListener, Input, OnDestroy, OnInit, Output } from '@angular/core';
ChangeDetectionStrategy, import { Subscription } from 'rxjs';
ChangeDetectorRef,
Component,
EventEmitter,
HostListener,
Input,
OnDestroy,
OnInit,
Output
} from '@angular/core';
import { filter, Subject, takeUntil, tap } from 'rxjs';
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../services/config/directive-config.service';
@ -19,6 +9,7 @@ import { TranslateService } from '../../services/translate/translate.service';
import { LocalTrack } from 'livekit-client'; import { LocalTrack } from 'livekit-client';
import { CustomDevice } from '../../models/device.model'; import { CustomDevice } from '../../models/device.model';
import { LangOption } from '../../models/lang.model'; import { LangOption } from '../../models/lang.model';
import { StorageService } from '../../services/storage/storage.service';
/** /**
* @internal * @internal
@ -26,9 +17,7 @@ import { LangOption } from '../../models/lang.model';
@Component({ @Component({
selector: 'ov-pre-join', selector: 'ov-pre-join',
templateUrl: './pre-join.component.html', templateUrl: './pre-join.component.html',
styleUrls: ['./pre-join.component.scss'], styleUrls: ['./pre-join.component.scss']
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
}) })
export class PreJoinComponent implements OnInit, OnDestroy { export class PreJoinComponent implements OnInit, OnDestroy {
@Input() set error(error: { name: string; message: string } | undefined) { @Input() set error(error: { name: string; message: string } | undefined) {
@ -45,22 +34,21 @@ export class PreJoinComponent implements OnInit, OnDestroy {
windowSize: number; windowSize: number;
isLoading = true; isLoading = true;
participantName: string | undefined = ''; participantName: string | undefined;
/** /**
* @ignore * @ignore
*/ */
isMinimal: boolean = false; isMinimal: boolean = false;
showCameraButton: boolean = true;
showMicrophoneButton: boolean = true;
showLogo: boolean = true; showLogo: boolean = true;
showParticipantName: boolean = true;
videoTrack: LocalTrack | undefined; videoTrack: LocalTrack | undefined;
audioTrack: LocalTrack | undefined; audioTrack: LocalTrack | undefined;
private tracks: LocalTrack[]; private tracks: LocalTrack[];
private log: ILogger; private log: ILogger;
private destroy$ = new Subject<void>(); private screenShareStateSubscription: Subscription;
private minimalSub: Subscription;
private displayLogoSub: Subscription;
private shouldRemoveTracksWhenComponentIsDestroyed: boolean = true; private shouldRemoveTracksWhenComponentIsDestroyed: boolean = true;
@HostListener('window:resize') @HostListener('window:resize')
@ -73,6 +61,7 @@ export class PreJoinComponent implements OnInit, OnDestroy {
private libService: OpenViduComponentsConfigService, private libService: OpenViduComponentsConfigService,
private cdkSrv: CdkOverlayService, private cdkSrv: CdkOverlayService,
private openviduService: OpenViduService, private openviduService: OpenViduService,
private storageService: StorageService,
private translateService: TranslateService, private translateService: TranslateService,
private changeDetector: ChangeDetectorRef private changeDetector: ChangeDetectorRef
) { ) {
@ -84,21 +73,20 @@ export class PreJoinComponent implements OnInit, OnDestroy {
await this.initializeDevices(); await this.initializeDevices();
this.windowSize = window.innerWidth; this.windowSize = window.innerWidth;
this.isLoading = false; this.isLoading = false;
this.changeDetector.markForCheck();
} }
// ngAfterContentChecked(): void { ngAfterContentChecked(): void {
// // this.changeDetector.detectChanges(); this.changeDetector.detectChanges();
// this.isLoading = false; }
// }
async ngOnDestroy() { async ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
this.cdkSrv.setSelector('body'); 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) { if (this.shouldRemoveTracksWhenComponentIsDestroyed) {
this.tracks?.forEach((track) => { this.tracks.forEach((track) => {
track.stop(); track.stop();
}); });
} }
@ -121,73 +109,37 @@ export class PreJoinComponent implements OnInit, OnDestroy {
this.cdkSrv.setSelector('#prejoin-container'); this.cdkSrv.setSelector('#prejoin-container');
} }
join() { joinSession() {
if (this.showParticipantName && !this.participantName) { if (!this.participantName) {
this._error = this.translateService.translate('PREJOIN.NICKNAME_REQUIRED'); this._error = this.translateService.translate('PREJOIN.NICKNAME_REQUIRED');
return; return;
} }
// Mark tracks as permanent for avoiding to be removed in ngOnDestroy // Mark tracks as permanent for avoiding to be removed in ngOnDestroy
this.shouldRemoveTracksWhenComponentIsDestroyed = false; this.shouldRemoveTracksWhenComponentIsDestroyed = false;
this.onReadyToJoin.emit();
// Assign participant name to the observable if it is defined
if (this.participantName) {
this.libService.updateGeneralConfig({ participantName: this.participantName });
// 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),
tap(() => this.onReadyToJoin.emit())
)
.subscribe();
} else {
// No participant name to set, emit immediately
this.onReadyToJoin.emit();
}
} }
onParticipantNameChanged(name: string) { onParticipantNameChanged(name: string) {
if (name) this.participantName = name; this.participantName = name;
} }
onEnterPressed() { onEnterPressed() {
this.join(); this.joinSession();
} }
private subscribeToPrejoinDirectives() { private subscribeToPrejoinDirectives() {
this.libService.minimal$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => { this.minimalSub = this.libService.minimal$.subscribe((value: boolean) => {
this.isMinimal = value; this.isMinimal = value;
this.changeDetector.markForCheck(); // this.cd.markForCheck();
}); });
this.displayLogoSub = this.libService.displayLogo$.subscribe((value: boolean) => {
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.showLogo = value; this.showLogo = value;
this.changeDetector.markForCheck(); // this.cd.markForCheck();
}); });
this.libService.participantName$.subscribe((value: string) => {
this.libService.participantName$.pipe(takeUntil(this.destroy$)).subscribe((value: string) => { if (value) this.participantName = value;
if (value) { // this.cd.markForCheck();
this.participantName = value;
this.changeDetector.markForCheck();
}
});
this.libService.prejoinDisplayParticipantName$.pipe(takeUntil(this.destroy$)).subscribe((value: boolean) => {
this.showParticipantName = value;
this.changeDetector.markForCheck();
}); });
} }

View File

@ -1,5 +1,5 @@
#session-container { #session-container {
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
/* min-width: 400px; */ /* min-width: 400px; */
height: 100%; height: 100%;
} }
@ -12,7 +12,7 @@
margin: auto; margin: auto;
text-align: -webkit-center; text-align: -webkit-center;
text-align: -moz-center; text-align: -moz-center;
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.sidenav-container { .sidenav-container {
@ -29,7 +29,7 @@
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 380px; width: 380px;
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
border-left: none; border-left: none;
position: absolute; position: absolute;
z-index: 1; z-index: 1;
@ -57,21 +57,21 @@
} }
.mat-drawer-container { .mat-drawer-container {
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
} }
#toolbar-container, #toolbar-container,
#footer-container { #footer-container {
background-color: var(--ov-background-color); background-color: var(--ov-primary-color);
/* min-width: 400px !important; */ /* min-width: 400px !important; */
width: 100%; width: 100%;
height: 70px; height: 70px;
} }
#footer { #footer {
color: var(--ov-text-primary-color); color: #ffffff;
height: 25px; height: 25px;
background-color: var(--ov-background-color); background-color: #333333;
padding: 0 14px 0 0; padding: 0 14px 0 0;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
@ -111,12 +111,12 @@
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
::ng-deep .mat-form-field-label { ::ng-deep .mat-form-field-label {
color: var(--ov-text-primary-color) !important; color: var(--ov-panel-text-color) !important;
} }
/* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of form-field that may no longer apply for the MDC version. */
::ng-deep .mat-mdc-form-field.mat-focused .mat-form-field-ripple { ::ng-deep .mat-mdc-form-field.mat-focused .mat-form-field-ripple {
background-color: var(--ov-text-primary-color) !important; background-color: var(--ov-panel-text-color) !important;
} }
::ng-deep .mat-drawer { ::ng-deep .mat-drawer {

View File

@ -16,7 +16,7 @@ import {
import { ILogger } from '../../models/logger.model'; import { ILogger } from '../../models/logger.model';
import { animate, style, transition, trigger } from '@angular/animations'; import { animate, style, transition, trigger } from '@angular/animations';
import { MatDrawerContainer, MatSidenav } from '@angular/material/sidenav'; 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 { SidenavMode } from '../../models/layout.model';
import { PanelStatusInfo, PanelType } from '../../models/panel.model'; import { PanelStatusInfo, PanelType } from '../../models/panel.model';
import { DataTopic } from '../../models/data-topic.model'; import { DataTopic } from '../../models/data-topic.model';
@ -46,9 +46,8 @@ import {
RoomEvent, RoomEvent,
Track Track
} from 'livekit-client'; } from 'livekit-client';
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model'; import { ParticipantModel } from '../../models/participant.model';
import { RecordingStatus } from '../../models/recording.model'; import { ServiceConfigService } from '../../services/config/service-config.service';
import { TemplateManagerService, SessionTemplateConfiguration } from '../../services/template/template-manager.service';
/** /**
* @internal * @internal
@ -59,43 +58,22 @@ import { TemplateManagerService, SessionTemplateConfiguration } from '../../serv
templateUrl: './session.component.html', templateUrl: './session.component.html',
styleUrls: ['./session.component.scss'], styleUrls: ['./session.component.scss'],
animations: [trigger('sessionAnimation', [transition(':enter', [style({ opacity: 0 }), animate('50ms', style({ opacity: 1 }))])])], animations: [trigger('sessionAnimation', [transition(':enter', [style({ opacity: 0 }), animate('50ms', style({ opacity: 1 }))])])],
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush
standalone: false
}) })
export class SessionComponent implements OnInit, OnDestroy { export class SessionComponent implements OnInit, OnDestroy {
@ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>; @ContentChild('toolbar', { read: TemplateRef }) toolbarTemplate: TemplateRef<any>;
@ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>; @ContentChild('panel', { read: TemplateRef }) panelTemplate: TemplateRef<any>;
@ContentChild('layout', { read: TemplateRef }) layoutTemplate: 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>(); @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>(); @Output() onParticipantCreated: EventEmitter<ParticipantModel> = new EventEmitter<ParticipantModel>();
/**
* 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>();
room: Room; room: Room;
sideMenu: MatSidenav; sideMenu: MatSidenav;
@ -104,20 +82,17 @@ export class SessionComponent implements OnInit, OnDestroy {
drawer: MatDrawerContainer; drawer: MatDrawerContainer;
loading: boolean = true; loading: boolean = true;
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: SessionTemplateConfiguration = {};
private shouldDisconnectRoomWhenComponentIsDestroyed: boolean = true; private shouldDisconnectRoomWhenComponentIsDestroyed: boolean = true;
private readonly SIDENAV_WIDTH_LIMIT_MODE = 790; private readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
private destroy$ = new Subject<void>(); private menuSubscription: Subscription;
private layoutWidthSubscription: Subscription;
private updateLayoutInterval: NodeJS.Timeout; private updateLayoutInterval: NodeJS.Timeout;
private captionLanguageSubscription: Subscription;
private log: ILogger; private log: ILogger;
private layoutService: LayoutService;
constructor( constructor(
private layoutService: LayoutService, private serviceConfig: ServiceConfigService,
private actionService: ActionService, private actionService: ActionService,
private openviduService: OpenViduService, private openviduService: OpenViduService,
private participantService: ParticipantService, private participantService: ParticipantService,
@ -130,16 +105,15 @@ export class SessionComponent implements OnInit, OnDestroy {
private translateService: TranslateService, private translateService: TranslateService,
// private captionService: CaptionService, // private captionService: CaptionService,
private backgroundService: VirtualBackgroundService, private backgroundService: VirtualBackgroundService,
private cd: ChangeDetectorRef, private cd: ChangeDetectorRef
private templateManagerService: TemplateManagerService
) { ) {
this.log = this.loggerSrv.get('SessionComponent'); this.log = this.loggerSrv.get('SessionComponent');
this.setupTemplates(); this.layoutService = this.serviceConfig.getLayoutService();
} }
@HostListener('window:beforeunload') @HostListener('window:beforeunload')
beforeunloadHandler() { beforeunloadHandler() {
this.disconnectRoom(ParticipantLeftReason.BROWSER_UNLOAD); this.disconnectRoom();
} }
@HostListener('window:resize') @HostListener('window:resize')
@ -188,39 +162,15 @@ export class SessionComponent implements OnInit, OnDestroy {
set layoutContainer(container: ElementRef) { set layoutContainer(container: ElementRef) {
setTimeout(async () => { setTimeout(async () => {
if (container) { if (container) {
if (this.libService.showBackgroundEffectsButton()) { // Apply background from storage when layout container is in DOM
// Apply background from storage when layout container is in DOM only when background effects button is enabled await this.backgroundService.applyBackgroundFromStorage();
await this.backgroundService.applyBackgroundFromStorage();
}
} }
}, 0); }, 0);
} }
async ngOnInit() { async ngOnInit() {
this.shouldDisconnectRoomWhenComponentIsDestroyed = true; this.room = this.openviduService.getRoom();
this.onRoomCreated.emit(this.room);
// 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.subscribeToCaptionLanguage(); // this.subscribeToCaptionLanguage();
this.subcribeToActiveSpeakersChanged(); this.subcribeToActiveSpeakersChanged();
@ -233,22 +183,19 @@ export class SessionComponent implements OnInit, OnDestroy {
// this.subscribeToParticipantNameChanged(); // this.subscribeToParticipantNameChanged();
this.subscribeToDataMessage(); this.subscribeToDataMessage();
this.subscribeToReconnection(); this.subscribeToReconnection();
this.subscribeToVirtualBackground();
// if (this.libService.isRecordingEnabled()) { if (this.libService.isRecordingEnabled()) {
// this.subscribeToRecordingEvents(); // this.subscribeToRecordingEvents();
// } }
// if (this.libService.isBroadcastingEnabled()) { if (this.libService.isBroadcastingEnabled()) {
// this.subscribeToBroadcastingEvents(); // this.subscribeToBroadcastingEvents();
// } }
try { try {
await this.participantService.connect(); 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.cd.markForCheck();
this.loading = false; this.loading = false;
this.onParticipantConnected.emit(this.participantService.getLocalParticipant()); this.onParticipantCreated.emit(this.participantService.getLocalParticipant());
} catch (error) { } catch (error) {
this.log.e('There was an error connecting to the room:', error.code, error.message); 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); 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() { async ngOnDestroy() {
if (this.shouldDisconnectRoomWhenComponentIsDestroyed) { 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.participantService.clear();
// this.room = undefined; // this.room = undefined;
this.destroy$.next(); if (this.menuSubscription) this.menuSubscription.unsubscribe();
this.destroy$.complete(); if (this.layoutWidthSubscription) this.layoutWidthSubscription.unsubscribe();
// if (this.captionLanguageSubscription) this.captionLanguageSubscription.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 // Mark session as disconnected for avoiding to do it again in ngOnDestroy
this.shouldDisconnectRoomWhenComponentIsDestroyed = false; this.shouldDisconnectRoomWhenComponentIsDestroyed = false;
await this.openviduService.disconnectRoom(() => { await this.openviduService.disconnectRoom();
this.onParticipantLeft.emit({
roomName: this.openviduService.getRoomName(),
participantName: this.participantService.getLocalParticipant()?.name || '',
identity: this.participantService.getLocalParticipant()?.identity || '',
reason
});
}, false);
} }
private subscribeToTogglingMenu() { private subscribeToTogglingMenu() {
@ -311,7 +239,7 @@ export class SessionComponent implements OnInit, OnDestroy {
this.startUpdateLayoutInterval(); 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) { if (this.sideMenu) {
this.settingsPanelOpened = ev.isOpened && ev.panelType === PanelType.SETTINGS; this.settingsPanelOpened = ev.isOpened && ev.panelType === PanelType.SETTINGS;
@ -330,7 +258,7 @@ export class SessionComponent implements OnInit, OnDestroy {
} }
private subscribeToLayoutWidth() { 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; 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}`); this.log.d(`Data event received: ${topic}`);
switch (topic) { switch (topic) {
case DataTopic.CHAT: case DataTopic.CHAT:
const participantName = participant?.name || 'Unknown'; const participantName = participant?.identity || 'Unknown';
this.chatService.addRemoteMessage(event.message, participantName); this.chatService.addRemoteMessage(event.message, participantName);
break; break;
case DataTopic.RECORDING_STARTING: case DataTopic.RECORDING_STARTING:
@ -500,12 +428,9 @@ export class SessionComponent implements OnInit, OnDestroy {
case DataTopic.ROOM_STATUS: case DataTopic.ROOM_STATUS:
const { recordingList, isRecordingStarted, isBroadcastingStarted, broadcastingId } = event as RoomStatusData; const { recordingList, isRecordingStarted, isBroadcastingStarted, broadcastingId } = event as RoomStatusData;
if (this.libService.showRecordingActivityRecordingsList()) { this.recordingService.setRecordingList(recordingList);
this.recordingService.setRecordingList(recordingList);
}
if (isRecordingStarted) { if (isRecordingStarted) {
const recordingActive = recordingList.find((recording) => recording.status === RecordingStatus.STARTED); this.recordingService.setRecordingStarted();
this.recordingService.setRecordingStarted(recordingActive);
} }
if (isBroadcastingStarted) { if (isBroadcastingStarted) {
this.broadcastingService.setBroadcastingStarted(broadcastingId); this.broadcastingService.setBroadcastingStarted(broadcastingId);
@ -518,84 +443,28 @@ export class SessionComponent implements OnInit, OnDestroy {
); );
} }
private subscribeToReconnection() { subscribeToReconnection() {
this.room.on(RoomEvent.Reconnecting, () => { this.room.on(RoomEvent.Reconnecting, () => {
this.log.w('Connection lost: Reconnecting'); this.log.w('Connection lost: Reconnecting');
this.actionService.openConnectionDialog( this.actionService.openConnectionDialog(
this.translateService.translate('ERRORS.CONNECTION'), this.translateService.translate('ERRORS.CONNECTION'),
this.translateService.translate('ERRORS.RECONNECT') this.translateService.translate('ERRORS.RECONNECT')
); );
this.onRoomReconnecting.emit();
}); });
this.room.on(RoomEvent.Reconnected, () => { this.room.on(RoomEvent.Reconnected, () => {
this.log.w('Connection lost: Reconnected'); this.log.w('Connection lost: Reconnected');
this.actionService.closeConnectionDialog(); this.actionService.closeConnectionDialog();
this.onRoomReconnected.emit();
}); });
this.room.on(RoomEvent.Disconnected, async (reason: DisconnectReason | undefined) => { this.room.on(RoomEvent.Disconnected, async (reason: DisconnectReason | undefined) => {
this.shouldDisconnectRoomWhenComponentIsDestroyed = false; if (reason === DisconnectReason.SERVER_SHUTDOWN) {
this.actionService.closeConnectionDialog(); this.log.e('Room Disconnected', reason);
const participantLeftEvent: ParticipantLeftEvent = { this.actionService.openConnectionDialog(
roomName: this.openviduService.getRoomName(), this.translateService.translate('ERRORS.CONNECTION'),
participantName: this.participantService.getLocalParticipant()?.name || '', this.translateService.translate('ERRORS.RECONNECT')
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)
); );
} }
}); // await this.disconnectRoom();
}
private subscribeToVirtualBackground() {
this.libService.backgroundEffectsButton$.subscribe(async (enable) => {
if (!enable && this.backgroundService.isBackgroundApplied()) {
await this.backgroundService.removeBackground();
if (this.panelService.isBackgroundEffectsPanelOpened()) {
this.panelService.closePanel();
}
}
}); });
} }

View File

@ -1,4 +1,4 @@
<div class="device-container-element" [class.mute-btn]="!isMicrophoneEnabled"> <div class="device-container-element">
<!-- <button mat-stroked-button [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger" id="audio-devices-menu"> <!-- <button mat-stroked-button [matMenuTriggerFor]="menu" #menuTrigger="matMenuTrigger" id="audio-devices-menu">
<mat-icon class="audio-icon">mic</mat-icon> <mat-icon class="audio-icon">mic</mat-icon>
<span class="device-label"> {{ microphoneSelected.label }} </span> <span class="device-label"> {{ microphoneSelected.label }} </span>
@ -22,7 +22,7 @@
id="microphone-button" id="microphone-button"
[disableRipple]="true" [disableRipple]="true"
[disabled]="!hasAudioDevices || microphoneStatusChanging" [disabled]="!hasAudioDevices || microphoneStatusChanging"
[class.mute-btn]="!isMicrophoneEnabled" [class.warn-btn]="!isMicrophoneEnabled"
(click)="toggleMic($event)" (click)="toggleMic($event)"
[matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)" [matTooltip]="isMicrophoneEnabled ? ('TOOLBAR.MUTE_AUDIO' | translate) : ('TOOLBAR.UNMUTE_AUDIO' | translate)"
[matTooltipDisabled]="!hasAudioDevices" [matTooltipDisabled]="!hasAudioDevices"
@ -46,7 +46,7 @@
<div id="audio-devices-form" *ngIf="microphones.length === 0"> <div id="audio-devices-form" *ngIf="microphones.length === 0">
<div id="mat-select-trigger"> <div id="mat-select-trigger">
<button mat-icon-button id="microphone-button" class="mute-btn" [disabled]="true"> <button mat-icon-button id="microphone-button" class="warn-btn" [disabled]="true">
<mat-icon id="mic_off"> mic_off </mat-icon> <mat-icon id="mic_off"> mic_off </mat-icon>
</button> </button>
<span id="audio-devices-not-found"> {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} </span> <span id="audio-devices-not-found"> {{ 'PREJOIN.NO_AUDIO_DEVICE' | translate }} </span>

View File

@ -1,13 +1,9 @@
$ov-selection-color-btn: #afafaf;
$ov-selection-color: #cccccc;
:host { :host {
--ov-light-color-darker: color-mix(in srgb, var(--ov-light-color), #000 15%);
.device-container-element { .device-container-element {
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
border: 1px solid $ov-selection-color-btn; border: 1px solid var(--ov-light-color-darker);
}
.device-container-element.mute-btn {
border: 1px solid var(--ov-error-color);
} }
#audio-devices-form { #audio-devices-form {
width: 100%; width: 100%;
@ -19,7 +15,7 @@ $ov-selection-color: #cccccc;
} }
#microphone-button { #microphone-button {
color:#000000 color: var(--ov-secondary-color);
} }
::ng-deep .mat-mdc-text-field-wrapper, ::ng-deep .mat-mdc-text-field-wrapper,
@ -35,15 +31,15 @@ $ov-selection-color: #cccccc;
::ng-deep .mat-mdc-text-field-wrapper { ::ng-deep .mat-mdc-text-field-wrapper {
padding-left: 0px; padding-left: 0px;
padding-right: 10px; padding-right: 10px;
background-color: $ov-selection-color !important; background-color: var(--ov-panel-background) !important;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
::ng-deep .mdc-button--unelevated { ::ng-deep .mdc-button--unelevated {
border-top-left-radius: var(--ov-surface-radius); border-top-left-radius: var(--ov-panel-radius);
border-bottom-left-radius: var(--ov-surface-radius); border-bottom-left-radius: var(--ov-panel-radius);
border-bottom-right-radius: 0px !important; border-bottom-right-radius: 0px !important;
border-top-right-radius: 0px !important; border-top-right-radius: 0px !important;
background-color: $ov-selection-color-btn !important; background-color: var(--ov-light-color-darker) !important;
width: 48px !important; width: 48px !important;
min-width: 48px !important; min-width: 48px !important;
padding: 0; padding: 0;
@ -76,28 +72,13 @@ $ov-selection-color: #cccccc;
} }
::ng-deep .mat-mdc-button-touch-target { ::ng-deep .mat-mdc-button-touch-target {
border-radius: var(--ov-surface-radius) !important; border-radius: var(--ov-panel-radius) !important;
} }
.mute-btn { .warn-btn {
color: #ffffff !important; color: var(--ov-warn-color) !important;
background-color: var(--ov-error-color) !important;
} }
} }
::ng-deep .mat-mdc-select-panel { ::ng-deep .mat-mdc-select-panel {
background-color: #ffffff !important; background-color: var(--ov-panel-background) !important;
}
::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;
} }

View File

@ -12,8 +12,7 @@ import { ParticipantModel } from '../../../models/participant.model';
@Component({ @Component({
selector: 'ov-audio-devices-select', selector: 'ov-audio-devices-select',
templateUrl: './audio-devices.component.html', templateUrl: './audio-devices.component.html',
styleUrls: ['./audio-devices.component.scss'], styleUrls: ['./audio-devices.component.scss']
standalone: false
}) })
export class AudioDevicesComponent implements OnInit, OnDestroy { export class AudioDevicesComponent implements OnInit, OnDestroy {
@Output() onAudioDeviceChanged = new EventEmitter<CustomDevice>(); @Output() onAudioDeviceChanged = new EventEmitter<CustomDevice>();

View File

@ -3,15 +3,15 @@
} }
.section-description { .section-description {
color: var(--ov-text-primary-color); color: var(--ov-panel-text-color);
font-size: 12px; font-size: 12px;
padding: 0px 5px; padding: 0px 5px;
display: block; display: block;
} }
.lang-button { .lang-button {
background-color: var(--ov-secondary-action-color); background-color: var(--ov-logo-background-color);
color: var(--ov-text-primary-color); color: var(--ov-text-color);
} }
.pro-feature { .pro-feature {
@ -24,7 +24,7 @@
/* TODO(mdc-migration): The following rule targets internal classes of slide-toggle that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of slide-toggle that may no longer apply for the MDC version. */
::ng-deep .mat-mdc-slide-toggle.mat-checked .mat-slide-toggle-bar { ::ng-deep .mat-mdc-slide-toggle.mat-checked .mat-slide-toggle-bar {
background-color: var(--ov-accent-action-color); background-color: var(--ov-tertiary-color);
} }
/* TODO(mdc-migration): The following rule targets internal classes of slide-toggle that may no longer apply for the MDC version. */ /* TODO(mdc-migration): The following rule targets internal classes of slide-toggle that may no longer apply for the MDC version. */

View File

@ -23,11 +23,11 @@ export class CaptionsSettingComponent implements OnInit, OnDestroy {
private captionsStatusSubs: Subscription; private captionsStatusSubs: Subscription;
private sttStatusSubs: Subscription; private sttStatusSubs: Subscription;
constructor( private layoutService: LayoutService;
private layoutService: LayoutService,
private captionService: CaptionService, constructor(private serviceConfig: ServiceConfigService, private captionService: CaptionService, private openviduService: OpenViduService) {
private openviduService: OpenViduService this.layoutService = this.serviceConfig.getLayoutService();
) {} }
ngOnInit(): void { ngOnInit(): void {
// this.isOpenViduPro = this.openviduService.isOpenViduPro(); // this.isOpenViduPro = this.openviduService.isOpenViduPro();

View File

@ -1,9 +1,9 @@
<button id="lang-btn-compact" *ngIf="compact" mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>translate</mat-icon>
</button>
<button *ngIf="!compact" mat-flat-button [matMenuTriggerFor]="menu" class="lang-button" id="lang-btn"> <button *ngIf="!compact" mat-flat-button [matMenuTriggerFor]="menu" class="lang-button" id="lang-btn">
<span id="lang-selected-name">{{ langSelected?.name }}</span> <span id="lang-selected-name">{{ langSelected?.name }}</span>
<mat-icon class="expand-more-icon">expand_more</mat-icon> <mat-icon>expand_more</mat-icon>
</button>
<button id="lang-btn-compact" *ngIf="compact" mat-icon-button [matMenuTriggerFor]="menu">
<mat-icon>translate</mat-icon>
</button> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button <button

View File

@ -1,21 +1,12 @@
$ov-surface-color-lighter: color-mix(in srgb, var(--ov-surface-color), #fff 5%); $ov-light-color-lighter: color-mix(in srgb, var(--ov-light-color), #fff 40%);
.lang-button { .lang-button {
background-color: var(--ov-primary-action-color) !important; background-color: var(--ov-secondary-color) !important;
color: var(--ov-secondary-action-color) !important; color: var(--ov-text-color);
}
.lang-button .mat-icon {
color: var(--ov-secondary-action-color);
} }
::ng-deep .mat-mdc-menu-panel { ::ng-deep .mat-mdc-menu-panel {
border-radius: var(--ov-surface-radius) !important; border-radius: var(--ov-panel-radius) !important;
background-color: $ov-surface-color-lighter !important; background-color: var(--ov-panel-background) !important;
border: 1px solid $ov-light-color-lighter !important;
box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2) !important; box-shadow: 1px 1px 5px 0px rgba(0, 0, 0, 0.2) !important;
} }
::ng-deep .mat-mdc-menu-item,
.mat-mdc-menu-item:visited,
.mat-mdc-menu-item:link {
color: var(--ov-text-surface-color) !important;
}

View File

@ -12,8 +12,7 @@ import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'ov-lang-selector', selector: 'ov-lang-selector',
templateUrl: './lang-selector.component.html', templateUrl: './lang-selector.component.html',
styleUrls: ['./lang-selector.component.scss'], styleUrls: ['./lang-selector.component.scss']
standalone: false
}) })
export class LangSelectorComponent implements OnInit, OnDestroy { export class LangSelectorComponent implements OnInit, OnDestroy {
/** /**

View File

@ -1,14 +1,14 @@
$ov-selection-color-btn: #afafaf;
$ov-selection-color: #cccccc;
:host { :host {
--ov-light-color-darker: color-mix(in srgb, var(--ov-light-color), #000 15%);
#name-input-container { #name-input-container {
height: 70px; height: 70px;
border-radius: var(--ov-surface-radius); border-radius: var(--ov-panel-radius);
} }
#name-input-container mat-form-field { #name-input-container mat-form-field {
width: 100%; width: 100%;
color: var(--ov-secondary-action-color); color: var(--ov-secondary-color);
} }
::ng-deep .mat-mdc-form-field-infix { ::ng-deep .mat-mdc-form-field-infix {
@ -18,18 +18,18 @@ $ov-selection-color: #cccccc;
::ng-deep .mat-mdc-text-field-wrapper { ::ng-deep .mat-mdc-text-field-wrapper {
padding: 0; padding: 0;
height: 70px; height: 70px;
background-color: $ov-selection-color !important; background-color: var(--ov-panel-background) !important;
border-radius: var(--ov-surface-radius);
} }
::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before { ::ng-deep .mdc-text-field--filled:not(.mdc-text-field--disabled) .mdc-line-ripple::before {
border: 0px !important; border: 0px !important;
} }
::ng-deep .mdc-button--unelevated { ::ng-deep .mdc-button--unelevated {
border-top-left-radius: var(--ov-surface-radius) !important; border-top-left-radius: var(--ov-panel-radius) !important;
border-bottom-left-radius: var(--ov-surface-radius) !important; border-bottom-left-radius: var(--ov-panel-radius) !important;
border-bottom-right-radius: 0px !important; border-bottom-right-radius: 0px !important;
border-top-right-radius: 0px !important; border-top-right-radius: 0px !important;
background-color: $ov-selection-color-btn !important; background-color: var(--ov-light-color-darker) !important;
width: 48px !important; width: 48px !important;
min-width: 48px !important; min-width: 48px !important;
padding: 0; padding: 0;
@ -38,7 +38,7 @@ $ov-selection-color: #cccccc;
.error { .error {
::ng-deep .mdc-button--unelevated { ::ng-deep .mdc-button--unelevated {
background-color: var(--ov-error-color) !important; background-color: var(--ov-warn-color) !important;
} }
} }
@ -47,21 +47,16 @@ $ov-selection-color: #cccccc;
width: 24px; width: 24px;
font-size: 24px !important; font-size: 24px !important;
margin: auto; margin: auto;
color: #000000 !important; color: var(--ov-secondary-color) !important;
} }
input { input {
padding-left: 10px !important; padding-left: 10px !important;
border-top-right-radius: var(--ov-surface-radius) !important; border-top-right-radius: var(--ov-panel-radius) !important;
border-bottom-right-radius: var(--ov-surface-radius) !important; border-bottom-right-radius: var(--ov-panel-radius) !important;
border-bottom-left-radius: 0px !important; border-bottom-left-radius: 0px !important;
border-top-left-radius: 0px !important; border-top-left-radius: 0px !important;
border: 1px solid $ov-selection-color-btn; border: 1px solid var(--ov-light-color-darker);
color: #000000; color: var(--ov-panel-text-color);
caret-color: #000000 !important;
} }
} }
::ng-deep .mat-mdc-form-field.mat-focused .mat-mdc-select-arrow {
color: var(--ov-primary-action-color) !important;
}

View File

@ -9,8 +9,7 @@ import { StorageService } from '../../../services/storage/storage.service';
@Component({ @Component({
selector: 'ov-participant-name-input', selector: 'ov-participant-name-input',
templateUrl: './participant-name-input.component.html', templateUrl: './participant-name-input.component.html',
styleUrls: ['./participant-name-input.component.scss'], styleUrls: ['./participant-name-input.component.scss']
standalone: false
}) })
export class ParticipantNameInputComponent implements OnInit { export class ParticipantNameInputComponent implements OnInit {
name: string; name: string;

View File

@ -1,4 +1,4 @@
<div class="device-container-element" [class.mute-btn]="!isCameraEnabled"> <div class="device-container-element">
<mat-form-field id="video-devices-form" *ngIf="cameras.length > 0"> <mat-form-field id="video-devices-form" *ngIf="cameras.length > 0">
<mat-select <mat-select
[disabled]="!hasVideoDevices" [disabled]="!hasVideoDevices"
@ -12,7 +12,7 @@
id="camera-button" id="camera-button"
[disableRipple]="true" [disableRipple]="true"
[disabled]="!hasVideoDevices || cameraStatusChanging" [disabled]="!hasVideoDevices || cameraStatusChanging"
[class.mute-btn]="!isCameraEnabled" [class.warn-btn]="!isCameraEnabled"
(click)="toggleCam($event)" (click)="toggleCam($event)"
[matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)" [matTooltip]="isCameraEnabled ? ('TOOLBAR.STOP_VIDEO' | translate) : ('TOOLBAR.START_VIDEO' | translate)"
[matTooltipDisabled]="!hasVideoDevices" [matTooltipDisabled]="!hasVideoDevices"
@ -31,7 +31,7 @@
<div id="video-devices-form" *ngIf="cameras.length === 0"> <div id="video-devices-form" *ngIf="cameras.length === 0">
<div id="mat-select-trigger"> <div id="mat-select-trigger">
<button mat-icon-button id="camera-button" class="mute-btn" [disabled]="true"> <button mat-icon-button id="camera-button" class="warn-btn" [disabled]="true">
<mat-icon id="videocam_off"> videocam_off </mat-icon> <mat-icon id="videocam_off"> videocam_off </mat-icon>
</button> </button>
<span id="video-devices-not-found"> {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} </span> <span id="video-devices-not-found"> {{ 'PREJOIN.NO_VIDEO_DEVICE' | translate }} </span>

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