openvidu-call: Added openvidu-call project as tutorial

This commit is contained in:
csantosm 2022-03-09 15:58:33 +01:00
parent d2af6e68b3
commit 85012630fb
72 changed files with 43199 additions and 0 deletions

52
openvidu-call/.gitignore vendored Normal file
View File

@ -0,0 +1,52 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# compiled output
*.angular
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
*/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
*.vscode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db
.editorconfig
*.browserslistrc
.git/*

15
openvidu-call/README.md Normal file
View File

@ -0,0 +1,15 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openvidu/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/fiware/orion.svg)](https://hub.docker.com/r/openvidu/openvidu-call/)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](http://openvidu.io)
openvidu-call
===
Visit [openvidu.io/demos](http://openvidu.io/demos#3)
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
[Documentation](https://docs.openvidu.io/en/latest/demos/openvidu-call/)

View File

@ -0,0 +1,5 @@
FROM openvidu/openvidu-dev-node:10.x
COPY run.sh /run.sh
ENTRYPOINT [ "/run.sh" ]

7
openvidu-call/ci/run.sh Executable file
View File

@ -0,0 +1,7 @@
#!/bin/bash -x
set -eu -o pipefail
git clone https://github.com/openvidu/openvidu-call
cd openvidu-call/front/openvidu-call/
npm install
./node_modules/protractor/bin/protractor ./e2e/protractor.conf.js --baseUrl=${APP_URL}

View File

@ -0,0 +1,52 @@
## OpenVidu Call docker deployment
OpenVidu bases its deployment on Docker **since 2.13.0 version**.
**NOTE: docker can be use with OpenVidu Call since 2.14.0 version and above.**
### Build OpenVidu Call container
You have several options to build it:
#### stable.dockerfile
The aim of this docker file is generate a docker image from a OpenVidu Call release.
You must add a `RELEASE_VERSION` that you want to build.
To build it:
```bash
docker build -f stable.dockerfile -t <your-tag-name> --build-arg RELEASE_VERSION=<your-release-version> .
```
#### prod.dockerfile
The aim of this docker file is generate a docker image from a OpenVidu Call branch.
To build it:
```bash
docker build -f prod.dockerfile -t <your-tag-name> --build-arg BRANCH_NAME=<branch-name> --build-arg BASE_HREF=<your-base-href>.
```
By default, the **BRANCH_NAME** name will be `master` and **BASE_HREF** will be `/`.
#### dev.dockerfile
The aim of this docker file is generate a docker image from a OpenVidu Call branch intalling the `openvidu-browser` with the latest changes from **master** branch.
```bash
docker build -f dev.dockerfile -t <your-tag-name> --build-arg BRANCH_NAME=<branch-name> --build-arg BASE_HREF=<your-base-href>.
```
### Run OpenVidu Call container
```
docker run -p 5000:<your_port> -e SERVER_PORT=<your_port> -e OPENVIDU_URL=<your_openvidu_url> -e OPENVIDU_SECRET=<your_secret> openvidu/openvidu-call:X.Y.Z
```
Go to **http://localhost:your_port**

View File

@ -0,0 +1,37 @@
# Build OpenVidu Call for production
FROM node:lts-alpine3.13 as openvidu-call-build
WORKDIR /openvidu-call
ARG BASE_HREF=/
RUN apk add wget git
COPY . .
# Build OpenVidu call
RUN rm openvidu-call-front/package-lock.json && \
rm openvidu-call-back/package-lock.json && \
# Install openvidu-call-front dependencies and build it for production
cd openvidu-call-front && npm install && \
cd ../ && npm run build-prod ${BASE_HREF} --prefix openvidu-call-front && \
rm -rf openvidu-call-front && \
# Install openvidu-call-back dependencies and build it for production
npm i --prefix openvidu-call-back && \
npm run build --prefix openvidu-call-back && \
mv openvidu-call-back/dist . && \
rm -rf openvidu-call-back openvidu
FROM node:lts-alpine3.13
WORKDIR /opt/openvidu-call
COPY --from=openvidu-call-build /openvidu-call/dist .
# Entrypoint
COPY docker/entrypoint.sh /usr/local/bin
RUN apk add curl && \
chmod +x /usr/local/bin/entrypoint.sh && \
npm install -g nodemon
# CMD /usr/local/bin/entrypoint.sh
CMD ["/usr/local/bin/entrypoint.sh"]

View File

@ -0,0 +1,15 @@
#!/bin/sh
[[ -z "${OPENVIDU_URL}" ]] && export OPENVIDU_URL=$(curl -s ifconfig.co)
[[ -z "${OPENVIDU_SECRET}" ]] && export OPENVIDU_SECRET=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
# openvidu-call configuration
cat>/opt/openvidu-call/.env<<EOF
SERVER_PORT=${SERVER_PORT}
OPENVIDU_URL=${OPENVIDU_URL}
OPENVIDU_SECRET=${OPENVIDU_SECRET}
CALL_OPENVIDU_CERTTYPE=${CALL_OPENVIDU_CERTTYPE}
EOF
cd /opt/openvidu-call
nodemon openvidu-call-server.js

View File

@ -0,0 +1,43 @@
# Build OpenVidu Call for production
FROM node:lts-alpine3.13 as openvidu-call-build
WORKDIR /openvidu-call
ARG BRANCH_NAME=master
ARG BASE_HREF=/
RUN apk add wget unzip
# Download openvidu-call from specific branch (master by default), intall openvidu-browser and build for production
RUN wget "https://github.com/OpenVidu/openvidu-call/archive/${BRANCH_NAME}.zip" -O openvidu-call.zip && \
unzip openvidu-call.zip && \
rm openvidu-call.zip && \
mv openvidu-call-${BRANCH_NAME}/openvidu-call-front/ . && \
mv openvidu-call-${BRANCH_NAME}/openvidu-call-back/ . && \
rm openvidu-call-front/package-lock.json && \
rm openvidu-call-back/package-lock.json && \
rm -rf openvidu-call-${BRANCH_NAME} && \
# Install openvidu-call-front dependencies and build it for production
npm i --prefix openvidu-call-front && \
npm run build-prod ${BASE_HREF} --prefix openvidu-call-front && \
rm -rf openvidu-call-front && \
# Install openvidu-call-back dependencies and build it for production
npm i --prefix openvidu-call-back && \
npm run build --prefix openvidu-call-back && \
mv openvidu-call-back/dist . && \
rm -rf openvidu-call-back
FROM node:lts-alpine3.13
WORKDIR /opt/openvidu-call
COPY --from=openvidu-call-build /openvidu-call/dist .
# Entrypoint
COPY ./entrypoint.sh /usr/local/bin
RUN apk add curl && \
chmod +x /usr/local/bin/entrypoint.sh && \
npm install -g nodemon
# CMD /usr/local/bin/entrypoint.sh
CMD ["/usr/local/bin/entrypoint.sh"]

37
openvidu-call/docker/run.sh Executable file
View File

@ -0,0 +1,37 @@
#!/usr/bin/env bash
if [[ -z "$1" ]] || [[ -z "$2" ]]; then
if [[ -z "$1" ]]; then
echo "RELEASE_VERSION argument is required" 1>&2
fi
if [[ -z "$2" ]]; then
echo "BRANCH_NAME argument is required" 1>&2
fi
echo "Example of use: ./run.sh 2.14.0 master" 1>&2
exit 1
fi
RELEASE_VERSION=$1
BRANCH_NAME=$2
CALL_BASE_HREF=/
DEMOS_BASE_HREF=/openvidu-call/
printf '\n'
printf '\n -------------------------------------------------------------'
printf '\n Installing OpenVidu Call with the following arguments:'
printf '\n'
printf '\n Call container tag: openvidu/openvidu-call:%s' "${RELEASE_VERSION}"
printf '\n Demos container tag: openvidu/openvidu-call-demos:%s' "${RELEASE_VERSION}"
printf '\n Branch to build: %s' "${BRANCH_NAME}"
printf '\n -------------------------------------------------------------'
printf '\n'
docker build -f prod.dockerfile -t openvidu/openvidu-call:${RELEASE_VERSION} --build-arg BRANCH_NAME=${BRANCH_NAME} --build-arg BASE_HREF=${CALL_BASE_HREF} .
docker build -f prod.dockerfile -t openvidu/openvidu-call:${RELEASE_VERSION}-demos --build-arg BRANCH_NAME=${BRANCH_NAME} --build-arg BASE_HREF=${DEMOS_BASE_HREF} .
printf '\n'
printf '\n Pushing containers to OpenVidu DockerHub'
printf '\n'
docker push openvidu/openvidu-call:${RELEASE_VERSION}
docker push openvidu/openvidu-call:${RELEASE_VERSION}-demos

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,56 @@
{
"author": "OpenVidu",
"bugs": {
"url": "https://github.com/OpenVidu/openvidu-call/issues"
},
"dependencies": {
"btoa": "1.2.1",
"dotenv": "14.2.0",
"express": "4.17.2",
"openvidu-node-client": "2.20.2"
},
"description": "OpenVidu Call Server",
"devDependencies": {
"@types/btoa": "1.2.3",
"@types/express": "4.17.13",
"@types/jest": "27.4.0",
"@types/node": "17.0.10",
"concurrently": "7.0.0",
"cross-env": "7.0.3",
"dotenv-webpack": "7.0.3",
"jest": "27.4.7",
"nodemon": "2.0.15",
"supertest": "6.2.2",
"ts-loader": "9.2.6",
"ts-node": "10.4.0",
"typescript": "4.4.4",
"webpack": "5.66.0",
"webpack-cli": "4.9.1",
"webpack-node-externals": "3.0.0"
},
"homepage": "https://github.com/OpenVidu/openvidu-call#readme",
"jest": {
"coveragePathIgnorePatterns": [
"/node_modules/"
],
"testEnvironment": "node"
},
"keywords": [
"openvidu",
"webrtc",
"openvidu-call"
],
"license": "Apache-2.0",
"main": "app.ts",
"name": "openvidu-call-server",
"repository": {
"type": "git",
"url": "git+https://github.com/OpenVidu/openvidu-call.git"
},
"scripts": {
"build": "webpack",
"start": "cross-env CALL_OPENVIDU_CERTTYPE=selfsigned nodemon src/app.ts",
"test": "jest"
},
"version": "2.20.0"
}

View File

@ -0,0 +1,30 @@
import * as express from 'express';
import { SERVER_PORT, OPENVIDU_URL, OPENVIDU_SECRET, CALL_OPENVIDU_CERTTYPE } from './config';
import {app as callController} from './controllers/CallController';
import * as dotenv from 'dotenv';
dotenv.config();
const app = express();
app.use(express.static('public'));
app.use(express.json());
app.use('/call', callController);
// Accept selfsigned certificates if CALL_OPENVIDU_CERTTYPE=selfsigned
if (CALL_OPENVIDU_CERTTYPE === 'selfsigned') {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"
}
app.listen(SERVER_PORT, () => {
console.log("---------------------------------------------------------");
console.log(" ")
console.log(`OPENVIDU URL: ${OPENVIDU_URL}`);
console.log(`OPENVIDU SECRET: ${OPENVIDU_SECRET}`);
console.log(`CALL OPENVIDU CERTTYPE: ${CALL_OPENVIDU_CERTTYPE}`);
console.log(`OpenVidu Call Server is listening on port ${SERVER_PORT}`);
console.log(" ")
console.log("---------------------------------------------------------");
});

View File

@ -0,0 +1,4 @@
export const SERVER_PORT = process.env.SERVER_PORT || 5000;
export const OPENVIDU_URL = process.env.OPENVIDU_URL || 'https://localhost:4443';
export const OPENVIDU_SECRET = process.env.OPENVIDU_SECRET || 'MY_SECRET';
export const CALL_OPENVIDU_CERTTYPE = process.env.CALL_OPENVIDU_CERTTYPE;

View File

@ -0,0 +1,39 @@
import * as express from 'express';
import { Request, Response } from 'express';
import { Session } from 'openvidu-node-client';
import { OpenViduService } from '../services/OpenViduService';
import { OPENVIDU_URL } from '../config';
export const app = express.Router({
strict: true
});
const openviduService = new OpenViduService();
app.post('/', async (req: Request, res: Response) => {
let sessionId: string = req.body.sessionId;
let nickname: string = req.body.nickname;
let createdSession: Session = null;
console.log('Session ID received', sessionId);
try {
createdSession = await openviduService.createSession(sessionId);
} catch (error) {
handleError(error, res);
return;
}
try {
const connection = await openviduService.createConnection(createdSession, nickname);
res.status(200).send(JSON.stringify(connection.token));
} catch (error) {
handleError(error, res);
}
});
function handleError(error: any, res: Response){
try {
let statusCode = parseInt(error.message);
res.status(parseInt(error.message)).send(`OpenVidu Server returned an error to OpenVidu Call Server: ${statusCode}`)
} catch (error) {
res.status(503).send('Cannot connect with OpenVidu Server');
}
}

View File

@ -0,0 +1,27 @@
import { Connection, ConnectionProperties, OpenVidu, Session, SessionProperties } from "openvidu-node-client";
import { OPENVIDU_URL, OPENVIDU_SECRET } from '../config';
export class OpenViduService {
private openvidu: OpenVidu;
constructor(){
this.openvidu = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET);
}
public async createSession(sessionId: string): Promise<Session> {
console.log("Creating session: ", sessionId);
let sessionProperties: SessionProperties = {customSessionId: sessionId};
return await this.openvidu.createSession(sessionProperties);
}
public async createConnection(session: Session, nickname: string): Promise<Connection> {
console.log(`Requesting token for session ${session.sessionId}`);
let connectionProperties: ConnectionProperties = {}
if(!!nickname) {
connectionProperties.data = JSON.stringify({ openviduCustomConnectionId: nickname });
}
console.log('Connection Properties:', connectionProperties);
return await session.createConnection(connectionProperties);
}
}

View File

@ -0,0 +1,66 @@
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
// "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
// "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
"sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./dist", /* Redirect output structure to the directory. */
// "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
// "strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
// "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
// "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"noUnusedLocals": true,
"skipLibCheck": true
},
"exclude": ["**/*.test.ts", "*.test.tsx"]
}

View File

@ -0,0 +1,29 @@
const path = require('path');
const Dotenv = require('dotenv-webpack');
module.exports = {
entry: './src/app.ts',
mode: 'production',
target: 'node',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'openvidu-call-server.js'
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: [
new Dotenv()
],
module: {
rules: [
{
test: /\.ts$/,
use: [
'ts-loader',
]
}
]
}
}

View File

@ -0,0 +1,10 @@
{
"singleQuote": true,
"printWidth": 140,
"trailingComma": "none",
"semi": true,
"bracketSpacing": true,
"useTabs": true,
"jsxSingleQuote": true,
"tabWidth": 4
}

View File

@ -0,0 +1,30 @@
# OpenviduCall
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 6.0.1.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
Use the `--configuration` flag for a set the environment file.
For example: `--configuration=production`. Moreover, inside of `environment.production.ts` should exist the `openvidu_url` and `openvidu_secret` fields. Where `openvidu_url` will be the url where openvidu exist and its port. For example `https://call.openvidu.io:4443`
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

View File

@ -0,0 +1,172 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"openvidu-call": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist/openvidu-call",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
},
"configurations": {
"development": {
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false
},
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
},
"ci": {
"budgets": [
{
"type": "anyComponentStyle",
"maximumWarning": "6kb"
}
],
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.ci.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "openvidu-call:build",
"proxyConfig": "src/proxy.conf.json"
},
"configurations": {
"development": {
"browserTarget": "openvidu-call:build:development"
},
"production": {
"browserTarget": "openvidu-call:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "openvidu-call:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"codeCoverage": true,
"styles": [],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
],
"codeCoverageExclude": [
"/**/*mock*.ts",
"/**/openvidu-layout.ts"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
}
}
},
"openvidu-call-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "openvidu-call:serve"
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}
},
"defaultProject": "openvidu-call"
}

View File

@ -0,0 +1,46 @@
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
exports.config = {
allScriptsTimeout: 30000,
specs: ['./src/**/*.e2e-spec.ts'],
multiCapabilities: [
{
browserName: 'chrome',
chromeOptions: {
args: ['use-fake-ui-for-media-stream', 'use-fake-device-for-media-stream'],
},
acceptInsecureCerts : true
},
// {
// browserName: 'firefox',
// 'moz:firefoxOptions': {
// 'prefs': {
// 'media.navigator.streams.fake': true,
// 'media.navigator.permission.disabled': true
// }
// },
// acceptInsecureCerts : true
// }
],
restartBrowserBetweenTests: true,
directConnect: !process.env.SELENIUM_URL,
seleniumAddress: process.env.SELENIUM_URL,
baseUrl: (process.env.APP_URL || 'http://localhost:4200/'),
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {},
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json'),
}
);
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
},
};

View File

@ -0,0 +1,552 @@
import { OpenViduCall } from './call.po';
import { browser, by, ProtractorBrowser, Key, WebElement } from 'protractor';
import { protractor } from 'protractor/built/ptor';
describe('Connect to the room', () => {
const OVC = new OpenViduCall();
beforeEach(() => {
browser.waitForAngularEnabled(false);
browser.get('#/');
});
it('should navigate to OpenVidu room', () => {
const input = OVC.getRoomInput(browser);
input.clear();
input.sendKeys('OpenVidu');
OVC.getRoomJoinButton(browser).click();
expect(browser.getCurrentUrl()).toMatch('#/OpenVidu');
});
it('should show a short room name error', () => {
const input = OVC.getRoomInput(browser);
input.clear();
input.sendKeys('OV');
const shortError = OVC.getShortRoomNameError(browser);
expect(shortError.isDisplayed()).toBeTruthy();
OVC.getRoomJoinButton(browser).click();
expect(browser.getCurrentUrl()).toMatch('#/');
});
it('should show a required name room error', async () => {
const input = OVC.getRoomInput(browser);
await input.sendKeys(Key.CONTROL, 'a');
await input.sendKeys(Key.DELETE);
expect(OVC.getRequiredRoomNameError(browser).isDisplayed()).toBeTruthy();
OVC.getRoomJoinButton(browser).click();
expect(browser.getCurrentUrl()).toMatch('#/');
});
});
describe('Testing config card', () => {
const OVC = new OpenViduCall();
const EC = protractor.ExpectedConditions;
beforeEach(() => {
browser.waitForAngularEnabled(false);
browser.get('#/OpenVidu');
});
it('should show the config card', () => {
const configCard = OVC.getConfigCard(browser);
browser.wait(EC.visibilityOf(configCard), 3000);
expect(configCard.isDisplayed()).toBeTruthy();
});
it('should close the config card and go to home', () => {
browser.wait(EC.visibilityOf(OVC.getConfigCard(browser)), 3000);
expect(OVC.getConfigCard(browser).isDisplayed()).toBeTruthy();
browser.wait(EC.elementToBeClickable(OVC.getCloseButtonConfigCard(browser)), 5000);
OVC.getCloseButtonConfigCard(browser).click();
expect(browser.getCurrentUrl()).toMatch('#/');
// browser.wait(EC.elementToBeClickable(OVC.getCamButton(browser)), 5000);
// OVC.getCamButton(browser).click();
// browser.wait(EC.visibilityOf(OVC.getCamIcon(browser)), 5000);
// expect(OVC.getCamIcon(browser).isDisplayed()).toBeTruthy();
});
it('should be able to mute the camera', async () => {
let isVideoEnabled: boolean;
const videoEnableScript =
'const videoTrack = document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0]; return videoTrack.enabled;';
browser.wait(EC.elementToBeClickable(OVC.getConfigCardCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
isVideoEnabled = await browser.executeScript(videoEnableScript);
expect(isVideoEnabled).toBe(true);
OVC.getConfigCardCameraButton(browser).click();
isVideoEnabled = await browser.executeScript(videoEnableScript);
expect(isVideoEnabled).toBe(false);
});
it('should be able to mute the microphone', async () => {
let isAudioEnabled: boolean;
const audioEnableScript =
'const audioTrack = document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0]; return audioTrack.enabled;';
browser.wait(EC.elementToBeClickable(OVC.getConfigCardMicrophoneButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
isAudioEnabled = await browser.executeScript(audioEnableScript);
expect(isAudioEnabled).toBe(true);
OVC.getConfigCardMicrophoneButton(browser).click();
isAudioEnabled = await browser.executeScript(audioEnableScript);
expect(isAudioEnabled).toBe(false);
});
it('should be able to share the screen', async () => {
browser.wait(EC.elementToBeClickable(OVC.getConfigCardScreenShareButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardScreenShareButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(2);
});
});
it('should be able to share the screen and remove the camera video if it is muted', () => {
browser.wait(EC.elementToBeClickable(OVC.getConfigCardScreenShareButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getConfigCardCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardScreenShareButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
});
it('should be able to add the camera video when the screen is active clicking on camera button', () => {
browser.wait(EC.elementToBeClickable(OVC.getConfigCardScreenShareButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getConfigCardCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardScreenShareButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(2);
});
});
it('should be able to add the camera video disabling screen share', () => {
browser.wait(EC.elementToBeClickable(OVC.getConfigCardScreenShareButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getConfigCardCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardScreenShareButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getConfigCardScreenShareButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
});
it('should be able to join to room', async () => {
browser.wait(EC.elementToBeClickable(OVC.getRoomJoinButton(browser)), 5000);
OVC.getRoomJoinButton(browser).click();
expect(OVC.getRoomContainer(browser).isDisplayed()).toBeTruthy();
});
});
describe('Testing room', () => {
const OVC = new OpenViduCall();
const EC = protractor.ExpectedConditions;
beforeEach(() => {
browser.waitForAngularEnabled(false);
browser.get('#/');
browser.wait(EC.elementToBeClickable(OVC.getRoomJoinButton(browser)), 5000);
OVC.getRoomJoinButton(browser).click();
browser.sleep(1000);
browser.wait(EC.elementToBeClickable(OVC.getRoomJoinButton(browser)), 5000);
OVC.getRoomJoinButton(browser).click();
browser.sleep(1000);
});
afterEach(() => {
browser.wait(EC.elementToBeClickable(OVC.getLeaveButton(browser)), 5000);
OVC.getLeaveButton(browser).click();
expect(expect(browser.getCurrentUrl()).toMatch('#/'));
});
it('should be able to mute the camera', async () => {
let isVideoEnabled: boolean;
const videoEnableScript =
'const videoTrack = document.getElementsByTagName("video")[0].srcObject.getVideoTracks()[0]; return videoTrack.enabled;';
browser.wait(EC.elementToBeClickable(OVC.getRoomCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
isVideoEnabled = await browser.executeScript(videoEnableScript);
expect(isVideoEnabled).toBe(true);
OVC.getRoomCameraButton(browser).click();
isVideoEnabled = await browser.executeScript(videoEnableScript);
expect(isVideoEnabled).toBe(false);
// Uncomment when muted video is shown
// expect(OVC.getCameraStatusDisabled(browser).isDisplayed()).toBe(true);
});
it('should be able to mute the microphone', async () => {
let isAudioEnabled: boolean;
const audioEnableScript =
'const audioTrack = document.getElementsByTagName("video")[0].srcObject.getAudioTracks()[0]; return audioTrack.enabled;';
browser.wait(EC.elementToBeClickable(OVC.getRoomMicrophoneButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
isAudioEnabled = await browser.executeScript(audioEnableScript);
expect(isAudioEnabled).toBe(true);
OVC.getRoomMicrophoneButton(browser).click();
isAudioEnabled = await browser.executeScript(audioEnableScript);
expect(isAudioEnabled).toBe(false);
expect(OVC.getMicrophoneStatusDisabled(browser).isDisplayed()).toBe(true);
});
it('should be able to share the screen', () => {
browser.wait(EC.elementToBeClickable(OVC.getRoomScreenButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
browser.sleep(3000);
OVC.getRoomScreenButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(2);
});
});
it('should be able to share the screen and remove the camera video if it is muted', () => {
browser.wait(EC.elementToBeClickable(OVC.getRoomScreenButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getRoomCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomScreenButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
});
it('should be able to add the camera video when the screen is active clicking on camera button', () => {
browser.wait(EC.elementToBeClickable(OVC.getRoomScreenButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getRoomCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomScreenButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
browser.sleep(5000);
OVC.getRoomCameraButton(browser).click();
browser.sleep(1000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(2);
});
});
it('should be able to add the camera video disabling screen share', () => {
browser.wait(EC.elementToBeClickable(OVC.getRoomScreenButton(browser)), 5000);
browser.wait(EC.elementToBeClickable(OVC.getRoomCameraButton(browser)), 5000);
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomCameraButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomScreenButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
OVC.getRoomScreenButton(browser).click();
OVC.getAllVideos(browser).then((videos) => {
expect(videos.length).toEqual(1);
});
});
it('should enable and disable fullscreen', () => {
browser.wait(EC.elementToBeClickable(OVC.getFullscreenButton(browser)), 5000);
const button = OVC.getFullscreenButton(browser);
button.click();
browser.sleep(1000);
browser.driver
.manage()
.window()
.getSize()
.then((value) => {
expect(value.width === OVC.getVideo(browser).width && value.height === OVC.getVideo(browser).height);
button.click();
browser.driver
.manage()
.window()
.getSize()
.then((value2) => {
expect(value2.width !== OVC.getVideo(browser).width && value2.height !== OVC.getVideo(browser).height);
});
});
});
});
// describe('Test room ', () => {
// const OVC = new OpenViduCall();
// const EC = protractor.ExpectedConditions;
// beforeEach(() => {
// browser.waitForAngularEnabled(false);
// browser.get('#/codeURJC');
// });
// it('should set disabled the webcam and show the icon', () => {
// browser.sleep(3000);
// browser.wait(EC.elementToBeClickable(OVC.getCamButton(browser)), 5000);
// OVC.getCamButton(browser).click();
// browser.wait(EC.visibilityOf(OVC.getCamIcon(browser)), 5000);
// expect(OVC.getCamIcon(browser).isDisplayed()).toBeTruthy();
// });
// it('should set disabled the microphone and show the icon', () => {
// browser.sleep(3000);
// browser.wait(EC.elementToBeClickable(OVC.getMicButton(browser)), 5000);
// OVC.getMicButton(browser).click();
// browser.wait(EC.visibilityOf(OVC.getMicIcon(browser)), 5000);
// expect(OVC.getMicIcon(browser).isDisplayed()).toBeTruthy();
// });
// it('should show the screen share dialog', () => {
// browser.wait(EC.elementToBeClickable(OVC.getShareScreenButton(browser)), 5000);
// OVC.getShareScreenButton(browser).click();
// browser.wait(EC.presenceOf(OVC.getDialogExtension(browser)), 5000);
// expect(OVC.getDialogExtension(browser).isDisplayed()).toBeTruthy();
// const button = OVC.getDialogCancelButton(browser);
// button.click();
// });
// it('should change the username', () => {
// browser.wait(EC.elementToBeClickable(OVC.getLocalNickname(browser)), 5000);
// OVC.getLocalNickname(browser).click();
// expect(OVC.getDialogNickname(browser).isDisplayed()).toBeTruthy();
// const inputDialog = OVC.getDialogNickname(browser).element(by.css('input'));
// inputDialog.clear();
// OVC.typeWithDelay(inputDialog, 'C');
// OVC.pressEnter(browser);
// browser.sleep(1000);
// expect(OVC.getLocalNickname(browser).getText()).toBe('C');
// });
// });
// describe('Chat component', () => {
// const OVC = new OpenViduCall();
// const EC = protractor.ExpectedConditions;
// beforeEach(() => {
// browser.waitForAngularEnabled(false);
// return browser.get('#/codeURJC');
// });
// it('should send a message', () => {
// browser.wait(EC.elementToBeClickable(OVC.getChatButton(browser)), 5000);
// OVC.getChatButton(browser).click();
// browser.sleep(1500);
// OVC.getChatInput(browser).sendKeys('Message 1');
// browser.actions().sendKeys(protractor.Key.ENTER).perform();
// expect(OVC.getMessageList(browser).count()).toEqual(1);
// OVC.getChatButton(browser).click();
// });
// });
// describe('Two browsers: ', () => {
// const OVC = new OpenViduCall();
// const EC = protractor.ExpectedConditions;
// let browser2: ProtractorBrowser;
// beforeEach(() => {
// browser.waitForAngularEnabled(false);
// browser.get('#/codeURJC');
// });
// it('should connect a new user', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// // avoid timeout waiting angular
// browser2.waitForAngularEnabled(false);
// browser.sleep(4000);
// expect(OVC.getVideoList(browser).count()).toEqual(2);
// OVC.closeSession(browser2);
// });
// it('a user should disconnect his WEBCAM and to be identified by other ', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// // avoid timeout waiting angular
// browser2.ignoreSynchronization = true;
// browser.wait(EC.elementToBeClickable(OVC.getCamButton(browser)), 10000);
// OVC.getCamButton(browser).click();
// expect(OVC.getCamIcon(browser).isDisplayed()).toBeTruthy();
// expect(OVC.getCamIcon(browser2).isDisplayed()).toBeTruthy();
// OVC.closeSession(browser2);
// });
// it('a user should disconnect his MICROPHONE and to be identified by other ', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// // avoid timeout waiting angular
// browser2.waitForAngularEnabled(false);
// browser.wait(EC.elementToBeClickable(OVC.getMicButton(browser)), 5000);
// OVC.getMicButton(browser).click();
// expect(OVC.getMicIcon(browser).isDisplayed()).toBeTruthy();
// expect(OVC.getMicIcon(browser2).isDisplayed()).toBeTruthy();
// OVC.closeSession(browser2);
// });
// it('a user should send a MESSAGE and to be identified by other ', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// // avoid timeout waiting angular
// browser2.waitForAngularEnabled(false);
// browser.sleep(3000);
// browser.wait(EC.elementToBeClickable(OVC.getChatButton(browser)), 5000);
// OVC.getChatButton(browser).click();
// browser.wait(EC.visibilityOf(OVC.getChatContent(browser)), 5000);
// expect(OVC.getChatContent(browser).isDisplayed).toBeTruthy();
// browser.sleep(5000);
// OVC.getChatInput(browser).click();
// OVC.getChatInput(browser).sendKeys('New Message');
// OVC.pressEnter(browser);
// OVC.getChatButton(browser).click();
// expect(OVC.getNewMessagePoint(browser2).getText()).toBe('1');
// OVC.closeSession(browser2);
// });
// it('both users should can type messages and reveive its', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// browser2.waitForAngularEnabled(false);
// OVC.getChatButton(browser).click();
// const input = OVC.getChatInput(browser);
// browser.sleep(2000);
// input.click();
// input.sendKeys('New Message User 1');
// OVC.pressEnter(browser);
// // OVC.getChatButton(browser).click();
// OVC.getChatButton(browser2).click();
// expect(OVC.getMessageList(browser2).count()).toEqual(1);
// const input2 = OVC.getChatInput(browser2);
// browser2.sleep(2000);
// input2.click();
// input2.sendKeys('Message User 2');
// OVC.pressEnter(browser2);
// expect(OVC.getMessageList(browser).count()).toEqual(4);
// OVC.closeSession(browser2);
// });
// it('user should can change his nickname and to be checked by other', () => {
// browser2 = OVC.openNewBrowserInTheSameRoom(browser);
// browser2.waitForAngularEnabled(false);
// browser.sleep(4000);
// OVC.getLocalNickname(browser).click();
// expect(OVC.getDialogNickname(browser).isDisplayed()).toBeTruthy();
// const inputDialog = OVC.getDialogNickname(browser).element(by.css('input'));
// inputDialog.click();
// inputDialog.clear();
// OVC.typeWithDelay(inputDialog, 'C');
// OVC.pressEnter(browser);
// browser.sleep(2000);
// expect(OVC.getRemoteNickname(browser2).getText()).toBe('C');
// OVC.closeSession(browser2);
// });
// });

View File

@ -0,0 +1,142 @@
'use strict';
import { by, element, protractor, ElementFinder, ProtractorBrowser, WebElement, ElementArrayFinder } from 'protractor';
export class OpenViduCall {
constructor() {}
getRoomInput(browser: ProtractorBrowser) {
return this.getElementById(browser, 'roomInput');
}
getRoomJoinButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'joinButton');
}
getShortRoomNameError(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'shortNameError');
}
getRequiredRoomNameError(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'requiredNameError');
}
getConfigCard(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'roomConfig');
}
getConfigCardScreenShareButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'configCardScreenButton');
}
getConfigCardCameraButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'configCardCameraButton');
}
getConfigCardMicrophoneButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'configCardMicrophoneButton');
}
getRoomContainer(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'room-container');
}
getCloseButtonConfigCard(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.id('closeButton'));
}
getAllVideos(browser: ProtractorBrowser){
return browser.element.all(by.tagName('video'));
}
getLeaveButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'navLeaveButton');
}
getRoomCameraButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'navCameraButton');
}
getCameraStatusDisabled(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'statusCam');
}
getFullscreenButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'fullscreenButton');
}
getRoomMicrophoneButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'navMicrophoneButton');
}
getMicrophoneStatusDisabled(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'statusMic');
}
getRoomScreenButton(browser: ProtractorBrowser): ElementFinder {
return this.getElementById(browser, 'navScreenButton');
}
openNewBrowserInTheSameRoom(browser: ProtractorBrowser): ProtractorBrowser {
return browser.forkNewDriverInstance(true);
}
getLocalNickname(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#localUser #nickname'));
}
getRemoteNickname(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#remoteUsers #nickname'));
}
getDialogNickname(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#dialogNickname'));
}
getChatButton(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#navChatButton'));
}
getVideo(browser: ProtractorBrowser): ElementFinder {
return this.getChatContent(browser).element(by.css('video'));
}
getRemoteVideoList(browser): ElementFinder {
return browser.element.all(by.css('#remoteUsers video'));
}
getChatContent(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#chatComponent'));
}
getChatInput(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.id('chatInput'));
}
getNewMessagePoint(browser: ProtractorBrowser): ElementFinder {
return browser.element(by.css('#mat-badge-content-0'));
}
pressEnter(browser: ProtractorBrowser) {
browser.actions().sendKeys(protractor.Key.ENTER).perform();
}
getMessageList(browser: ProtractorBrowser) {
return browser.element.all(by.css('#chatComponent .message-wrap .message .msg-detail'));
}
closeSession(browser: ProtractorBrowser) {
const leaveButton = this.getLeaveButton(browser);
leaveButton.click();
browser.quit();
}
typeWithDelay(input, keys: string) {
keys.split('').forEach((c) => input.sendKeys(c));
}
private getElementById(browser: ProtractorBrowser, id: string) {
return browser.element(by.id(id));
}
}

View File

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,52 @@
{
"dependencies": {
"@angular/animations": "13.1.2",
"@angular/cdk": "13.1.2",
"@angular/common": "13.1.2",
"@angular/compiler": "13.1.2",
"@angular/core": "13.1.2",
"@angular/forms": "13.1.2",
"@angular/material": "13.1.2",
"@angular/platform-browser": "13.1.2",
"@angular/platform-browser-dynamic": "13.1.2",
"@angular/router": "13.1.2",
"openvidu-angular": "file:openvidu-angular-2.20.0.tgz",
"openvidu-browser": "2.21.0",
"rxjs": "7.5.2",
"unique-names-generator": "4.6.0",
"zone.js": "0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "13.1.3",
"@angular/cli": "13.1.3",
"@angular/compiler-cli": "13.1.2",
"@angular/language-service": "13.1.2",
"@types/jasmine": "3.10.3",
"@types/node": "16.11.9",
"codelyzer": "6.0.1",
"fs-extra": "9.0.1",
"jasmine-core": "3.10.1",
"karma": "6.3.9",
"ng-packagr": "13.1.3",
"protractor": "7.0.0",
"ts-node": "10.4.0",
"tslib": "2.3.1",
"tslint": "6.1.3",
"typedoc": "0.22.11",
"typescript": "4.4.4"
},
"name": "openvidu-call",
"private": true,
"scripts": {
"build": "./node_modules/@angular/cli/bin/ng build && npm run copy:backend",
"build-prod": "func() { ./node_modules/@angular/cli/bin/ng.js build --configuration production --base-href=\"${1:-/}\" && npm run copy:backend-prod; }; func",
"copy:backend": "cp -a dist/openvidu-call/. ../openvidu-call-back/public/",
"copy:backend-prod": "mkdir -p ../openvidu-call-back/dist/public && cp -a dist/openvidu-call/. ../openvidu-call-back/dist/public/",
"e2e": "ng e2e",
"lint": "ng lint",
"start": "ng serve --configuration development",
"start:https": "ng serve --host 192.168.1.161 --ssl --ssl-key cert.key --ssl-cert cert.crt",
"test": "ng test openvidu-call --watch=false --code-coverage"
},
"version": "2.22.0-beta1"
}

View File

@ -0,0 +1,15 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { CallComponent } from './components/call/call.component';
import { HomeComponent } from './components/home/home.component';
const routes: Routes = [
{ path: '', component: HomeComponent },
{ path: ':roomName', component: CallComponent }
];
@NgModule({
imports: [RouterModule.forRoot(routes, { useHash: true })],
exports: [RouterModule]
})
export class AppRoutingModule {}

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,2 @@
<!--The content below is only a placeholder and can be replaced.-->
<router-outlet></router-outlet>

View File

@ -0,0 +1,15 @@
import { TestBed, async } from '@angular/core/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AppComponent]
}).compileComponents();
}));
it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'OpenVidu Call';
}

View File

@ -0,0 +1,50 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { environment } from 'src/environments/environment';
// Material
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { MatToolbarModule } from '@angular/material/toolbar';
// OpenVidu Components
// import {
// OpenviduComponentsLibraryModule,
// UserSettingsComponent,
// ToolbarComponent,
// RoomComponent,
// LayoutComponent
// } from 'openvidu-components-library';
// Application Components
import { AppComponent } from './app.component';
import { CallComponent } from './components/call/call.component';
import { HomeComponent } from './components/home/home.component';
import { OpenViduAngularConfig, OpenViduAngularModule } from 'openvidu-angular';
// Services
const config: OpenViduAngularConfig = {
production: environment.production
};
@NgModule({
declarations: [AppComponent, HomeComponent, CallComponent],
imports: [
BrowserModule,
BrowserAnimationsModule,
FormsModule,
ReactiveFormsModule,
MatToolbarModule,
OpenViduAngularModule.forRoot(config),
AppRoutingModule // Order is important, AppRoutingModule must be the last import for useHash working
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule {
ngDoBootstrap() {}
}

View File

@ -0,0 +1,5 @@
<ov-videoconference
(onJoinButtonClicked)="onJoinButtonClicked()"
(onToolbarLeaveButtonClicked)="onLeaveButtonClicked()"
[tokens]="tokens"
></ov-videoconference>

View File

@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { CallComponent } from './call.component';
describe('CallComponent', () => {
let component: CallComponent;
let fixture: ComponentFixture<CallComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ CallComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(CallComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,48 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Params, Router } from '@angular/router';
import { ParticipantService } from 'openvidu-angular';
import { RestService } from '../../services/rest.service';
@Component({
selector: 'app-call',
templateUrl: './call.component.html',
styleUrls: ['./call.component.css']
})
export class CallComponent implements OnInit {
sessionId = '';
tokens: { webcam: string; screen: string };
joinSessionClicked: boolean = false;
closeClicked: boolean = false;
isSessionAlive: boolean = false;
constructor(private restService: RestService, private participantService: ParticipantService, private router: Router, private route: ActivatedRoute) {}
ngOnInit() {
this.route.params.subscribe((params: Params) => {
this.sessionId = params.roomName;
});
}
async onJoinButtonClicked() {
let nickname;
const regex = /^UNSAFE_DEBUG_USE_CUSTOM_IDS_/gm;
const match = regex.exec(this.sessionId);
if(match && match.length > 0){
console.warn('DEBUGGING SESSION');
nickname = this.participantService.getLocalParticipant().getNickname();
}
this.tokens = {
webcam: await this.restService.getToken(this.sessionId, nickname),
screen: await this.restService.getToken(this.sessionId, nickname)
};
}
onLeaveButtonClicked() {
this.isSessionAlive = false;
this.closeClicked = true;
this.router.navigate([`/`]);
}
}

View File

@ -0,0 +1,179 @@
.section1 {
background: url('../../../assets/images/bg.webp') top center no-repeat;
background-size: cover;
height: 100%;
text-align: center;
position: relative;
color: #fff;
}
#header, .footer {
background-color: transparent;
color: #ffffff;
}
.roomError{
font-size: 16px;
color: #c01515;
}
.footer {
position: absolute;
bottom: 0;
font-size: 9px;
height: auto;
}
.footer a {
color: #ffffff;
}
#header_img {
max-width: 150px;
margin-right: 10px;
margin-top: 10px;
padding: 15px;
}
.ovVersion {
position: absolute;
right: 15px;
font-size: 16px;
font-weight: bold;
}
.ovLogo {
margin: auto;
}
#card_content {
height: 50%;
}
h4 {
font-size: 25px;
font-weight: 500;
color: #fff;
position: relative;
padding-bottom: 5px;
}
#room_card {
color: #303030;
position: inherit;
max-width: 700px;
width: 75%;
margin: auto;
background: rgba(221, 221, 221, 0.856);
}
#room_card mat-form-field {
margin: auto;
padding: 0px 5px;
}
/* Extra small devices (phones, 600px and down) */
@media only screen and (max-width: 600px) {
#header_img, .ovVersion, .footer {
display: none;
}
.joinForm, .ovLogo {
max-width: 80%;
}
.inputForm, h4 {
font-size: 16px ;
}
}
.container {
position: relative;
width: 100%;
padding: 100px 0;
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
}
.formContainer {
font-size: 20px;
text-align: center;
}
.joinForm {
min-width: 300px;
margin: 10px auto;
max-width: 50%;
background: #fff;
padding: 6px 10px;
position: relative;
border-radius: 10px 5px 10px 0px;
text-align: left;
}
.inputForm {
border: 0;
padding: 4px 8px;
font-size: 17px;
width: calc(100% - 90px);
outline: none;
caret-color: #000000;
color: #303030;
}
.joinButton {
position: absolute;
top: 0;
right: -2px;
bottom: 0;
border: 0;
background: none;
font-size: 16px;
font-weight: bold;
padding: 0 20px;
background: #00ad4e;
color: #fff;
border-radius: 0px 5px 10px 0px;
transition: 0.3s;
box-shadow: 0px 2px 15px rgba(0, 0, 0, 0.1);
margin: 0px;
}
.joinButton:hover {
background: #009242;
}
/* Small devices (portrait tablets and large phones, 600px and up) */
@media only screen and (min-width: 600px) {
.ovLogo {
max-width: 75%;
}
}
/* Medium devices (landscape tablets, 768px and up) */
@media only screen and (min-width: 768px) {
}
/* Large devices (laptops/desktops, 992px and up) */
@media only screen and (min-width: 992px) {
.ovLogo {
max-width: 60%;
}
.inputForm {
font-size: 20px !important;
}
}
/* Extra large devices (large laptops and desktops, 1200px and up) */
@media only screen and (min-width: 1200px) {
.section1 {
background-attachment: fixed;
}
.ovLogo {
max-width: 50%;
}
}

View File

@ -0,0 +1,51 @@
<div class="section1">
<mat-toolbar id="header">
<a href="https://openvidu.io/" target="_blank">
<img id="header_img" alt="OpenVidu Logo" src="assets/images/openvidu_logo.png" />
</a>
<div class="ovVersion">
<span>{{ version }}</span>
</div>
</mat-toolbar>
<div class="container">
<div class="ovInfo">
<img class="ovLogo" alt="OpenVidu Logo" src="assets/images/openvidu_logo.png" />
<h4>Videoconference rooms in one click</h4>
</div>
<div class="formContainer">
<div class="roomError" id="requiredNameError" *ngIf="roomForm.hasError('required')">
Room name is <strong>required</strong>
</div>
<div class="roomError" id="shortNameError" *ngIf="roomForm.hasError('minlength')">
Room name is <strong>too short!</strong>
</div>
<form [formGroup]="roomForm" novalidate (ngSubmit)="goToVideoCall()">
<div class="joinForm">
<input matInput class="inputForm" type="text" [formControl]="roomForm" autocomplete="off" id="roomInput" />
<button type="submit" class="joinButton" id="joinButton" >JOIN</button>
</div>
</form>
</div>
</div>
<mat-toolbar class="footer">
<span>
Photo by
<a
rel="noopener noreferrer"
target="_blank"
href="https://unsplash.com/@danielleone?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"
>
Daniel Leone
</a>
on
<a
href="https://unsplash.com/s/photos/mountain?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText"
target="_blank"
>
Unsplash
</a></span
>
</mat-toolbar>
</div>

View File

@ -0,0 +1,28 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { HomeComponent } from './home.component';
import { FormBuilder } from '@angular/forms';
describe('HomeComponent', () => {
let component: HomeComponent;
let fixture: ComponentFixture<HomeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
providers: [FormBuilder],
imports: [RouterTestingModule.withRoutes([])]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,31 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, Validators, FormControl } from '@angular/forms';
import { Router } from '@angular/router';
import { uniqueNamesGenerator, adjectives, colors, animals } from 'unique-names-generator';
import packageInfo from '../../../../package.json';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {
public roomForm: FormControl;
public version: string;
constructor(private router: Router, public formBuilder: FormBuilder) {}
ngOnInit() {
this.version = packageInfo.version;
const randomName = uniqueNamesGenerator({ dictionaries: [adjectives, colors, animals], separator: '-', });
this.roomForm = new FormControl(randomName, [Validators.minLength(4), Validators.required]);
}
public goToVideoCall() {
if (this.roomForm.valid) {
const roomName = this.roomForm.value.replace(/ /g, '-'); // replace white spaces by -
this.roomForm.setValue(roomName);
this.router.navigate(['/', roomName]);
}
}
}

View File

@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { RestService } from './rest.service';
describe('RestService', () => {
let service: RestService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(RestService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { catchError, lastValueFrom } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class RestService {
private baseHref: string;
constructor(private http: HttpClient) {
this.baseHref = '/' + (!!window.location.pathname.split('/')[1] ? window.location.pathname.split('/')[1] + '/' : '');
}
async getToken(sessionId: string, nickname?: string): Promise<string> {
try {
return lastValueFrom(this.http.post<any>(this.baseHref + 'call', { sessionId, nickname }));
} catch (error) {
if (error.status === 404) {
throw { status: error.status, message: 'Cannot connect with backend. ' + error.url + ' not found' };
}
throw error;
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,5 @@
export const environment = {
production: true,
openvidu_url: '',
openvidu_secret: ''
};

View File

@ -0,0 +1,17 @@
// This file can be replaced during build by using the `fileReplacements` array.
// `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`.
// The list of file replacements can be found in `angular.json`.
export const environment = {
production: false,
openvidu_url: '',
openvidu_secret: ''
};
/*
* In development mode, to ignore zone related error stack frames such as
* `zone.run`, `zoneDelegate.invokeTask` for easier debugging, you can
* import the following file, but please comment it out in production mode
* because it will have performance impact when throw error
*/
// import 'zone.js/dist/zone-error'; // Included with Angular CLI.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>OpenviduCall</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<link rel="preconnect" href="https://fonts.gstatic.com" />
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet" />
</head>
<body class="mat-typography" id="body">
<app-root></app-root>
</body>
</html>

View File

@ -0,0 +1,69 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-notify-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
customLaunchers: {
ChromeHeadless: {
base: 'Chrome',
flags: [
'--headless',
'--disable-gpu',
'--disable-translate',
'--disable-extensions',
// Without a remote debugging port, Google Chrome exits immediately.
'--no-sandbox',
'--remote-debugging-port=9222',
'--js-flags="--max_old_space_size=4096"'
]
}
},
// coverageIstanbulReporter: {
// dir: require('path').join(__dirname, '../coverage'),
// reports: ['html', 'lcovonly', 'text-summary'],
// fixWebpackSourcePaths: true,
// verbose: true,
// thresholds: {
// emitWarning: false,
// global: {
// statements: 80,
// branches: 80,
// functions: 80,
// lines: 80
// },
// each: {
// statements: 80,
// branches: 80,
// functions: 80,
// lines: 80
// }
// }
// },
reporters: [
'progress',
'kjhtml',
'dots',
// 'coverage-istanbul', // (https://github.com/mattlewis92/karma-coverage-istanbul-reporter/issues/49, https://github.com/angular/angular-cli/issues/10940)
'notify'
],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['ChromeHeadless'],
singleRun: false
});
};

View File

@ -0,0 +1,12 @@
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { environment } from './environments/environment';
if (environment.production) {
enableProdMode();
}
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.log(err));

View File

@ -0,0 +1,82 @@
/**
* This file includes polyfills needed by Angular and is loaded before the app.
* You can add your own extra polyfills to this file.
*
* This file is divided into 2 sections:
* 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
* 2. Application imports. Files imported after ZoneJS that should be loaded before your main
* file.
*
* The current setup is for so-called "evergreen" browsers; the last versions of browsers that
* automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
* Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
*
* Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html
*/
/***************************************************************************************************
* BROWSER POLYFILLS
*/
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
// import 'core-js/es6/symbol';
// import 'core-js/es6/object';
// import 'core-js/es6/function';
// import 'core-js/es6/parse-int';
// import 'core-js/es6/parse-float';
// import 'core-js/es6/number';
// import 'core-js/es6/math';
// import 'core-js/es6/string';
// import 'core-js/es6/date';
// import 'core-js/es6/array';
// import 'core-js/es6/regexp';
// import 'core-js/es6/map';
// import 'core-js/es6/weak-map';
// import 'core-js/es6/set';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
import 'core-js/es7/reflect';
/**
* Web Animations `@angular/platform-browser/animations`
* Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
* Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone'; // Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/
// import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';
// (window as any).global = window;

View File

@ -0,0 +1,6 @@
{
"/": {
"target": "http://localhost:5000",
"secure": false
}
}

View File

@ -0,0 +1,62 @@
// Custom Theming for Angular Material
// For more information: https://material.angular.io/guide/theming
@use '@angular/material' as mat;
@import '~@angular/material/theming';
// Plus imports for other components in your app.
// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat.core();
// Define the palettes for your theme using the Material Design palettes available in palette.scss
// (imported above). For each palette, you can optionally specify a default, lighter, and darker
// hue. Available color palettes: https://material.io/design/color/
$openvidu-components-primary: mat-palette($mat-blue-grey, 50, 300);
$openvidu-components-accent: mat-palette($mat-amber, 500, 700, A100);
// The warn palette is optional (defaults to red).
$openvidu-components-warn: mat.define-palette(mat.$red-palette);
// Create the theme object. A theme consists of configurations for individual
// theming systems such as "color" or "typography".
$openvidu-components-theme: mat.define-light-theme((
color: (
primary: $openvidu-components-primary,
accent: $openvidu-components-accent,
warn: $openvidu-components-warn,
)
));
// Include theme styles for core and each component used in your app.
// Alternatively, you can import and @include the theme mixins for each component
// that you are using.
@include mat.all-component-themes($openvidu-components-theme);
/* You can add global styles to this file, and also import other style files */
html, body { height: 100%; overflow: hidden;}
body { margin: 0; font-family: 'Roboto','RobotoDraft',Helvetica,Arial,sans-serif;}
// Custom openvidu-components styles
:root {
--ov-primary-color: #303030;
--ov-secondary-color: #586063;
--ov-tertiary-color: #598eff;
--ov-warn-color: #EB5144;
--ov-accent-color: #ffae35;
--ov-dark-color: #1d1d1d;
--ov-dark-light-color: #43484A;
--ov-light-color: #ffffff;
--ov-light-dark-color: #f1f1f1;
--ov-buttons-radius: 50%; // border-radius property
--ov-leave-button-radius: 10px;
--ov-video-radius: 5px;
--ov-panel-radius: 5px;
}

View File

@ -0,0 +1,20 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';
declare const require: any;
// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);

View File

@ -0,0 +1,41 @@
@import '~@angular/material/theming';
// always include only once per project
@include mat-core();
$my-app-primary: mat-palette($mat-blue-grey, 50, 300);
$my-app-accent: mat-palette($mat-amber, 500, 700, A100);
$my-app-theme: mat-light-theme($my-app-primary, $my-app-accent);
// use our theme with angular-material-theme mixin
@include angular-material-theme($my-app-theme);
// specify theme class eg: <body class="my-theme"> ... </body>
.alternate-theme {
$alternate-primary: mat-palette($mat-grey, 800);
$alternate-secondary: mat-palette($mat-grey, 400);
$alternate-theme: mat-light-theme($alternate-primary, $alternate-secondary);
@include angular-material-theme($alternate-theme);
}
// Custom openvidu-components colors
:root {
--ov-primary-color: #303030;
--ov-secondary-color: #586063;
--ov-tertiary-color: #598eff;
--ov-warn-color: #EB5144;
--ov-accent-color: #ffae35;
--ov-dark-color: #1d1d1d;
--ov-dark-light-color: #43484A;
--ov-light-color: #ffffff;
--ov-light-dark-color: #f1f1f1;
}

View File

@ -0,0 +1,18 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"files": [
"main.ts",
"polyfills.ts"
],
"include": [
"src/**/*.d.ts"
],
"exclude": [
"**/*.mock.ts",
"**/*.spec.ts"
]
}

View File

@ -0,0 +1,18 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -0,0 +1,32 @@
{
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"module": "esnext",
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"target": "es5",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2017",
"dom"
],
"paths": {
"core-js/es7/reflect": ["./node_modules/core-js/proposals/reflect-metadata"],
"openvidu-session": [
"dist/openvidu-session"
],
"openvidu-session/*": [
"dist/openvidu-session/*"
]
}
}
}

View File

@ -0,0 +1,130 @@
{
"rulesDirectory": [
"node_modules/codelyzer"
],
"rules": {
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warn"
},
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"import-spacing": true,
"indent": [
true,
"tabs", 4
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
],
"member-access": false,
"member-ordering": [
true,
{
"order": [
"static-field",
"instance-field",
"static-method",
"instance-method"
]
}
],
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": true,
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-input-rename": true,
"no-output-rename": true,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}

View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "{}"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright {yyyy} {name of copyright owner}
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,39 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![Documentation Status](https://readthedocs.org/projects/openvidu/badge/?version=stable)](https://docs.openvidu.io/en/stable/?badge=stable)
[![Docker badge](https://img.shields.io/docker/pulls/openvidu/openvidu-server-kms.svg)](https://hub.docker.com/r/openvidu/openvidu-server-kms)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](http://openvidu.io)
webcomponent-e2e
===
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120
### How to run it (localhost)
1) Start openvidu-server:
```
docker run -p 4443:4443 --rm -e OPENVIDU_SECRET=MY_SECRET openvidu/openvidu-server-kms:2.13.0
```
2) Start webcomponent app:
```
http-server web/
```
By default, the app will start on `http://localhost:8080`.
3) Install dependencies:
```
npm install
```
3) Run tests:
```
npm run test
```

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,18 @@
{
"name": "webcomponent-test-e2e",
"version": "1.0.0",
"description": "E2E webcomponent test",
"main": "index.js",
"scripts": {
"test": "mocha --recursive --timeout 30000 test.js"
},
"author": "Carlos Santos Morales",
"license": "Apache-2.0",
"dependencies": {
"chromedriver": "84.0.0",
"selenium-webdriver": "4.0.0-alpha.7"
},
"devDependencies": {
"mocha": "8.0.1"
}
}

View File

@ -0,0 +1,211 @@
require('chromedriver');
const assert = require('assert');
const webdriver = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const firefox = require('selenium-webdriver/firefox');
const { Builder, By, Key, promise, until } = require('selenium-webdriver');
const url = 'http://127.0.0.1:8080/#/';
const timeout = 5000;
const sleepTimeout = 500;
describe('Checkout localhost app', function () {
let browser;
let browser2;
var chromeOptions = new chrome.Options();
var chromeCapabilities = webdriver.Capabilities.chrome();
chromeOptions.addArguments(['--headless','--use-fake-ui-for-media-stream', '--use-fake-device-for-media-stream']);
chromeCapabilities.setAcceptInsecureCerts(true);
// var firefoxOptions = new firefox.Options();
// var firefoxCapabilities = webdriver.Capabilities.firefox();
// firefoxOptions.addArguments('--headless');
// firefoxOptions.setPreference('media.navigator.permission.disabled', true);
// firefoxOptions.setPreference('media.navigator.streams.fake', true);
// firefoxCapabilities.setAcceptInsecureCerts(true);
async function createChromeBrowser() {
return await new webdriver.Builder()
.forBrowser('chrome')
.withCapabilities(chromeCapabilities)
.setChromeOptions(chromeOptions)
.build();
}
async function createFirefoxBrowser() {
// return await new Builder()
// .forBrowser('firefox')
// .withCapabilities(firefoxCapabilities)
// .setFirefoxOptions(firefoxOptions)
// .build();
return await new webdriver.Builder()
.forBrowser('chrome2')
.withCapabilities(chromeCapabilities)
.setChromeOptions(chromeOptions)
.build();
}
beforeEach(async function() {
browser = await createChromeBrowser();
await browser.get(url);
});
// PUBLISHER EVENTS
it('should receive publisherCreated event', async function() {
try {
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
}finally {
await browser.quit();
}
});
it('should receive Publisher streamCreated event', async function () {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser.wait(until.elementLocated(By.id('publisher-streamCreated'), timeout))
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
}finally {
await browser.quit();
}
});
it('should receive Publisher streamPlaying event', async function() {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser.wait(until.elementLocated(By.id('publisher-streamPlaying'), timeout))
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
}finally {
await browser.quit();
}
});
// SESSION EVENTS
it('should receive REMOTE connectionCreated event', async() => {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
browser2 = await createFirefoxBrowser();
await browser2.get(url);
await browser2.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser2.sleep(sleepTimeout);
var user2 = await (await browser2.wait(until.elementLocated(By.id('nickname'), timeout))).getText();
await browser.wait(until.elementLocated(By.id(user2 + '-connectionCreated'), timeout));
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
await browser2.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
}finally {
await browser.quit();
await browser2.quit();
}
});
it('should receive REMOTE streamDestroyed event', async function() {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
browser2 = await createFirefoxBrowser();
await browser2.get(url);
await browser2.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser2.wait(until.elementLocated(By.id('publisher-streamPlaying'), timeout));
await browser2.sleep(sleepTimeout);
var user2 = await (await browser2.wait(until.elementLocated(By.id('nickname'), timeout))).getText();
await browser2.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
await browser.wait(until.elementLocated(By.id(user2 + '-streamDestroyed'), timeout));
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
} finally {
await browser.quit();
await browser2.quit();
}
});
it('should receive Session sessionDisconnected event', async function() {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser.wait(until.elementLocated(By.id('publisher-streamPlaying'), timeout));
await browser.sleep(sleepTimeout);
var user = await (await browser.wait(until.elementLocated(By.id('nickname'), timeout))).getText();
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
await browser.wait(until.elementLocated(By.id(user + '-sessionDisconnected'), timeout));
} catch (error) {
console.log(error);
}finally {
await browser.quit();
}
});
it('should receive REMOTE streamCreated event', async function() {
try {
// await browser.get(url);
await browser.wait(until.elementLocated(By.id('publisherCreated'), timeout));
browser2 = await createFirefoxBrowser();
await browser2.get(url);
await browser2.wait(until.elementLocated(By.id('publisherCreated'), timeout));
await browser2.wait(until.elementLocated(By.id('publisher-streamPlaying'), timeout));
await browser2.sleep(sleepTimeout);
var user2 = await (await browser2.wait(until.elementLocated(By.id('nickname'), timeout))).getText();
await browser.wait(until.elementLocated(By.id(user2 + '-streamCreated'), timeout));
await browser2.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
await browser.wait(until.elementLocated(By.id('navLeaveButton'), timeout)).click();
} catch (error) {
console.log(error);
}finally {
await browser.quit();
await browser2.quit();
}
});
// afterEach(async () => {
// if(browser){
// await browser.quit().catch(() => {});
// browser = null;
// }
// if(browser2){
// await browser2.quit().catch(() => {});
// browser2 = null;
// }
// });
});

View File

@ -0,0 +1,162 @@
$(document).ready(() => {
var webComponent = document.querySelector('openvidu-webcomponent');
webComponent.addEventListener('sessionCreated', (event) => {
var session = event.detail;
appendElement('sessionCreated');
// You can see the session documentation here
// https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/session.html
session.on('connectionCreated', (e) => {
console.error("connectionCreated", e);
var user = JSON.parse(e.connection.data).clientData;
appendElement(user + '-connectionCreated');
});
session.on('streamDestroyed', (e) => {
console.log("streamDestroyed", e);
var user = JSON.parse(e.stream.connection.data).clientData;
appendElement(user + '-streamDestroyed');
});
session.on('streamCreated', (e) => {
console.log("streamCreated", e);
var user = JSON.parse(e.stream.connection.data).clientData;
appendElement(user + '-streamCreated');
});
session.on('sessionDisconnected', (e) => {
console.warn("sessionDisconnected ", e);
var user = JSON.parse(e.target.connection.data).clientData;
appendElement(user + '-sessionDisconnected');
});
});
webComponent.addEventListener('publisherCreated', (event) => {
var publisher = event.detail;
appendElement('publisherCreated')
// You can see the publisher documentation here
// https://docs.openvidu.io/en/stable/api/openvidu-browser/classes/publisher.html
publisher.on('streamCreated', (e) => {
console.warn("Publisher streamCreated", e);
appendElement('publisher-streamCreated');
});
publisher.on('streamPlaying', (e) => {
appendElement('publisher-streamPlaying');
});
});
webComponent.addEventListener('error', (event) => {
console.log('Error event', event.detail);
});
var user = 'user' + Math.floor(Math.random() * 100);
joinSession('webcomponentTestE2ESession', user);
});
function appendElement(id) {
var eventsDiv = document.getElementById('events');
var element = document.createElement('div');
element.setAttribute("id", id);
element.setAttribute("style", "height: 200px;");
eventsDiv.appendChild(element);
}
async function joinSession(sessionName, user) {
var webComponent = document.querySelector('openvidu-webcomponent');
var tokens = [];
var token1 = await getToken(sessionName)
var token2 = await getToken(sessionName);
tokens.push(token1, token2);
var ovSettings = {
chat: true,
autopublish: true,
toolbarButtons: {
audio: true,
video: true,
screenShare: false,
fullscreen: true,
layoutSpeaking: true,
exit: true,
}
};
webComponent.sessionConfig = { sessionName, user, tokens, ovSettings };
}
/**
* --------------------------
* SERVER-SIDE RESPONSIBILITY
* --------------------------
* These methods retrieve the mandatory user token from OpenVidu Server.
* This behavior MUST BE IN YOUR SERVER-SIDE IN PRODUCTION (by using
* the API REST, openvidu-java-client or openvidu-node-client):
* 1) Initialize a session in OpenVidu Server (POST /api/sessions)
* 2) Generate a token in OpenVidu Server (POST /api/tokens)
* 3) Configure OpenVidu Web Component in your client side with the token
*/
var OPENVIDU_SERVER_URL = "https://localhost:4443" ;
var OPENVIDU_SERVER_SECRET = 'MY_SECRET';
function getToken(sessionName) {
return createSession(sessionName).then((sessionId) => createToken(sessionId));
}
function createSession(sessionName) { // See https://docs.openvidu.io/en/stable/reference-docs/REST-API/#post-apisessions
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: OPENVIDU_SERVER_URL + '/api/sessions',
data: JSON.stringify({ customSessionId: sessionName }),
headers: {
Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + OPENVIDU_SERVER_SECRET),
'Content-Type': 'application/json',
},
success: (response) => resolve(response.id),
error: (error) => {
if (error.status === 409) {
resolve(sessionName);
} else {
console.warn('No connection to OpenVidu Server. This may be a certificate error at ' + OPENVIDU_SERVER_URL);
if (
window.confirm(
'No connection to OpenVidu Server. This may be a certificate error at "' +
OPENVIDU_SERVER_URL +
'"\n\nClick OK to navigate and accept it. ' +
'If no certificate warning is shown, then check that your OpenVidu Server is up and running at "' +
OPENVIDU_SERVER_URL +
'"',
)
) {
location.assign(OPENVIDU_SERVER_URL + '/accept-certificate');
}
}
},
});
});
}
function createToken(sessionId) {
// See https://docs.openvidu.io/en/stable/reference-docs/REST-API/#post-apitokens
return new Promise((resolve, reject) => {
$.ajax({
type: 'POST',
url: OPENVIDU_SERVER_URL + '/api/tokens',
data: JSON.stringify({ session: sessionId }),
headers: {
Authorization: 'Basic ' + btoa('OPENVIDUAPP:' + OPENVIDU_SERVER_SECRET),
'Content-Type': 'application/json',
},
success: (response) => resolve(response.token),
error: (error) => reject(error),
});
});
}

View File

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>openvidu-web-component</title>
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
<script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
<script src="app.js"></script>
<script src="openvidu-webcomponent-2.14.0.js"></script>
<link rel="stylesheet" href="openvidu-webcomponent-2.14.0.css">
</head>
<body>
<div id="events"></div>
<!-- OpenVidu Web Component -->
<openvidu-webcomponent></openvidu-webcomponent>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long