OpenVidu v3

pull/835/head
pabloFuente 2024-07-02 19:19:05 +02:00
parent d9bc9b3700
commit eccc37156a
1002 changed files with 32871 additions and 145337 deletions

View File

@ -1,248 +0,0 @@
#!/bin/bash -x
set -eu -o pipefail
############################################################################
# Any function offered by this file that is not path agnostic assumes that #
# the path is located where the first command of each function requires it #
############################################################################
CLEAN_ENVIRONMENT=false
BUILD_OV_BROWSER=false
BUILD_OV_NODE_CLIENT=false
BUILD_OV_JAVA_CLIENT=false
BUILD_OV_PARENT=false
BUILD_OV_TESTAPP=false
BUILD_OV_SERVER_DASHBOARD=false
BUILD_OV_SERVER=false
BUILD_OV_SERVER_DEPENDENCY=false
BUILD_OV_SERVER_PRO_INSPECTOR=false
BUILD_OV_SERVER_PRO=false
CHECK_AND_PREPARE_KURENTO_SNAPSHOT=false
if [[ -n ${1:-} ]]; then
case "${1:-}" in
--clean-environment)
CLEAN_ENVIRONMENT=true
;;
--build-openvidu-browser)
BUILD_OV_BROWSER=true
;;
--build-openvidu-node-client)
BUILD_OV_NODE_CLIENT=true
;;
--build-openvidu-java-client)
BUILD_OV_JAVA_CLIENT=true
;;
--build-openvidu-parent)
BUILD_OV_PARENT=true
;;
--build-openvidu-testapp)
BUILD_OV_TESTAPP=true
;;
--build-openvidu-server-dashboard)
if [[ -z "${2:-}" ]]; then
echo "Must provide LINK_LOCAL_DEPENDENCIES as 1st parameter"
exit 1
fi
BUILD_OV_SERVER_DASHBOARD=true
LINK_LOCAL_DEPENDENCIES="${2}"
;;
--build-openvidu-server)
BUILD_OV_SERVER=true
;;
--build-openvidu-server-dependency)
BUILD_OV_SERVER_DEPENDENCY=true
;;
--build-openvidu-server-pro-inspector)
if [[ -z "${2:-}" ]]; then
echo "Must provide LINK_LOCAL_DEPENDENCIES as 1st parameter"
exit 1
fi
BUILD_OV_SERVER_PRO_INSPECTOR=true
LINK_LOCAL_DEPENDENCIES="${2}"
;;
--build-openvidu-server-pro)
BUILD_OV_SERVER_PRO=true
;;
--check-and-prepare-kurento-snapshot)
CHECK_AND_PREPARE_KURENTO_SNAPSHOT=true
;;
*)
echo "Unrecognized method $1"
exit 1
;;
esac
else
echo "Must provide a method to execute as first parameter when calling the script"
exit 1
fi
# -------------
# Clean environment
# -------------
if [[ "${CLEAN_ENVIRONMENT}" == true ]]; then
# Remove all running containers except test container and runner container
ids=$(docker ps -a -q)
for id in $ids; do
DOCKER_IMAGE=$(docker inspect --format='{{.Config.Image}}' $id)
if [[ "${DOCKER_IMAGE}" != *"$TEST_IMAGE"* ]] &&
[[ "${DOCKER_IMAGE}" != *"runner-image"* ]]; then
echo "Removing container image '$DOCKER_IMAGE' with id '$id'"
docker stop $id && docker rm $id
fi
done
# Clean /opt/openvidu contents
rm -rf /opt/openvidu/*
fi
# -------------
# Build openvidu-browser
# -------------
if [[ "${BUILD_OV_BROWSER}" == true ]]; then
pushd openvidu-browser || exit 1
npm install
npm run build
npm link
npm pack
mv openvidu-browser-*.tgz /opt/openvidu
npm run browserify
npm run browserify-prod
popd
fi
# -------------
# Build openvidu-node-client
# -------------
if [[ "${BUILD_OV_NODE_CLIENT}" == true ]]; then
pushd openvidu-node-client
npm install
npm run build
npm link
npm pack
mv openvidu-node-client-*.tgz /opt/openvidu
popd
fi
# -------------
# Build openvidu-java-client
# -------------
if [[ "${BUILD_OV_JAVA_CLIENT}" == true ]]; then
pushd openvidu-java-client
MVN_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
mvn -B clean compile package
mvn -B install:install-file -Dfile=target/openvidu-java-client-${MVN_VERSION}.jar \
-DgroupId=io.openvidu \
-DartifactId=openvidu-java-client \
-Dversion=${MVN_VERSION} -Dpackaging=jar
popd
fi
# -------------
# Build openvidu-parent
# -------------
if [[ "${BUILD_OV_PARENT}" == true ]]; then
mvn -B -DskipTests=true -Dmaven.artifact.threads=1 clean install
fi
# -------------
# Build openvidu-testapp
# -------------
if [[ "${BUILD_OV_TESTAPP}" == true ]]; then
pushd openvidu-testapp
npm install
npm link openvidu-browser openvidu-node-client
export NG_CLI_ANALYTICS="false" && ./node_modules/@angular/cli/bin/ng.js build --configuration production --output-path=/opt/openvidu/testapp
popd
fi
# -------------
# Build openvidu-server dashboard
# -------------
if [[ "${BUILD_OV_SERVER_DASHBOARD}" == true ]]; then
pushd openvidu-server/src/dashboard
npm install
if [[ "${LINK_LOCAL_DEPENDENCIES}" == true ]]; then
npm link openvidu-browser openvidu-node-client
fi
npm run build-prod
popd
fi
# -------------
# Build openvidu-server
# -------------
if [[ "${BUILD_OV_SERVER}" == true ]]; then
pushd openvidu-server
mvn -B -DskipTests=true clean package
mv target/openvidu-server-*.jar /opt/openvidu
popd
fi
# -------------
# Build openvidu-server dependency
# -------------
if [[ "${BUILD_OV_SERVER_DEPENDENCY}" == true ]]; then
pushd openvidu-server
mvn -B -DskipTests=true -Pdependency clean install
popd
fi
# -------------
# Build Inspector
# -------------
if [[ "${BUILD_OV_SERVER_PRO_INSPECTOR}" == true ]]; then
pushd dashboard
npm install
if [[ "${LINK_LOCAL_DEPENDENCIES}" == true ]]; then
npm link openvidu-browser openvidu-node-client
fi
npm run build-server-prod
popd
fi
# -------------
# Build openvidu-server-pro
# -------------
if [[ "${BUILD_OV_SERVER_PRO}" == true ]]; then
pushd openvidu-server-pro
mvn -B -DskipTests=true clean package
mv target/openvidu-server-pro-*.jar /opt/openvidu
popd
fi
# -------------
# Check kurento version from pom.xml
# If kurento version is a snapshot, configure snapshot builds
# -------------
if [[ "${CHECK_AND_PREPARE_KURENTO_SNAPSHOT}" == true ]]; then
# Check if kurento version is a snapshot
KURENTO_VERSION=$(awk -F'[<>]' '/<version.kurento>/ {print $3}' pom.xml)
if [[ "${KURENTO_VERSION}" == *"-SNAPSHOT" ]] && [[ -n "${KURENTO_SNAPSHOTS_URL:-}" ]]; then
echo "Kurento version is a SNAPSHOT: ${KURENTO_VERSION}"
mkdir -p /etc/maven
chmod -R 777 /etc/maven
pushd /etc/maven
rm -f settings.xml
curl https://raw.githubusercontent.com/OpenVidu/openvidu/master/ci-scripts/kurento-snapshots.xml -o settings.xml
sed -i "s|KURENTO_SNAPSHOTS_URL|${KURENTO_SNAPSHOTS_URL}|g" settings.xml
popd
else
echo "Kurento version is not a SNAPSHOT: ${KURENTO_VERSION}"
fi
fi

View File

@ -1,307 +0,0 @@
#!/bin/bash -x
set -eu -o pipefail
############################################################################
# Any function offered by this file that is not path agnostic assumes that #
# the path is located where the first command of each function requires it #
############################################################################
# Bump versions
BUMP_NPM_PROJECT_VERSION=false
BUMP_NPM_DEPENDENCY_VERSION=false
BUMP_MAVEN_PROJECT_VERSION=false
BUMP_MAVEN_PROPERTY_VERSION=false
BUMP_MAVEN_DEPENDENCY_VERSION=false
BUMP_DOCKER_COMPOSE_SERVICE_VERSION=false
BUMP_DOCKER_COMPOSE_HEADER_VERSION=false
BUMP_DOCKER_IMAGE_VERSION_IN_FILES=false
BUMP_APPLICATION_PROPERTIES_VAR_VALUE=false
WAIT_FOR_NPM_DEPENDENCY=false
GENERIC_SED=false
if [[ -n ${1:-} ]]; then
case "${1:-}" in
--bump-npm-project-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide VERSION as 1st parameter"
exit 1
fi
BUMP_NPM_PROJECT_VERSION=true
VERSION="${2}"
;;
--bump-npm-dependency-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide DEPENDENCY as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide VERSION as 2nd parameter"
exit 1
fi
BUMP_NPM_DEPENDENCY_VERSION=true
DEPENDENCY="${2}"
VERSION="${3}"
TYPE_OF_DEPENDENCY="${4:-dependencies}" # [dependencies, devDependencies, peerDependencies, optionalDependencies]
;;
--bump-maven-project-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide VERSION as 1st parameter"
exit 1
fi
BUMP_MAVEN_PROJECT_VERSION=true
VERSION="${2}"
;;
--bump-maven-property-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide PROPERTY as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide VERSION as 2nd parameter"
exit 1
fi
BUMP_MAVEN_PROPERTY_VERSION=true
PROPERTY="${2}"
VERSION="${3}"
;;
--bump-maven-dependency-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide DEPENDENCY as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide VERSION as 2nd parameter"
exit 1
fi
BUMP_MAVEN_DEPENDENCY_VERSION=true
DEPENDENCY="${2}"
VERSION="${3}"
;;
--bump-docker-compose-service-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide DOCKER_COMPOSE_FILE as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide SERVICE_IMAGE as 2nd parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide VERSION as 3rd parameter"
exit 1
fi
BUMP_DOCKER_COMPOSE_SERVICE_VERSION=true
DOCKER_COMPOSE_FILE="${2}"
SERVICE_IMAGE="${3}"
VERSION="${4}"
;;
--bump-docker-compose-header-version)
if [[ -z "${2:-}" ]]; then
echo "Must provide DOCKER_COMPOSE_FILE as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide HEADER as 2nd parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide VERSION as 3rd parameter"
exit 1
fi
BUMP_DOCKER_COMPOSE_HEADER_VERSION=true
DOCKER_COMPOSE_FILE="${2}"
HEADER="${3}"
VERSION="${4}"
;;
--bump-docker-image-version-in-files)
if [[ -z "${3:-}" ]]; then
echo "Must provide FILE_NAME_PATTERN as 1st parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide IMAGE as 2nd parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide VERSION as 3rd parameter"
exit 1
fi
BUMP_DOCKER_IMAGE_VERSION_IN_FILES=true
FILE_NAME_PATTERN="${2}"
IMAGE="${3}"
VERSION="${4}"
;;
--bump-application-properties-var-value)
if [[ -z "${3:-}" ]]; then
echo "Must provide APPLICATION_PROPERTIES_FILE as 2nd parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide VARIABLE as 3rd parameter"
exit 1
fi
if [[ -z "${4:-}" ]]; then
echo "Must provide VALUE as 4th parameter"
exit 1
fi
BUMP_APPLICATION_PROPERTIES_VAR_VALUE=true
APPLICATION_PROPERTIES_FILE="${2}"
VARIABLE="${3}"
VALUE="${4}"
;;
--wait-for-npm-dependency)
if [[ -z "${2:-}" ]]; then
echo "Must provide DEPENDENCY as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide VERSION as 2nd parameter"
exit 1
fi
WAIT_FOR_NPM_DEPENDENCY=true
DEPENDENCY="${2}"
VERSION="${3}"
;;
--generic-sed)
if [[ -z "${2:-}" ]]; then
echo "Must provide FILE as 1st parameter"
exit 1
fi
if [[ -z "${3:-}" ]]; then
echo "Must provide SED_EXPRESSION as 2nd parameter"
exit 1
fi
GENERIC_SED=true
FILE="${2}"
SED_EXPRESSION="${3}"
;;
*)
echo "Unrecognized method $1"
exit 1
;;
esac
else
echo "Must provide a method to execute as first parameter when calling the script"
exit 1
fi
compareFiles() {
if cmp -s "$1" "$1-AUX"; then
rm -f $1-AUX
echo "Error: no changes has been made to $1"
echo "Trying to change \"$2\" to \"$3\""
exit 1
else
cp -f "$1-AUX" "$1"
rm -f "$1-AUX"
fi
}
# -------------
# Bump NPM project version
# -------------
if [[ "${BUMP_NPM_PROJECT_VERSION}" == true ]]; then
npm version ${VERSION} --git-tag-version=false --commit-hooks=false
fi
# -------------
# Bump NPM project dependency
# -------------
if [[ "${BUMP_NPM_DEPENDENCY_VERSION}" == true ]]; then
jq -j ".${TYPE_OF_DEPENDENCY}.\"${DEPENDENCY}\" = \"${VERSION}\"" package.json >package.json-AUX
compareFiles package.json version $VERSION
fi
# -------------
# Bump Maven project version
# -------------
if [[ "${BUMP_MAVEN_PROJECT_VERSION}" == true ]]; then
cp pom.xml pom.xml-AUX
mvn -DskipTests=true versions:set -DnewVersion="${VERSION}"
mv pom.xml changed-pom.xml && mv pom.xml-AUX pom.xml && mv changed-pom.xml pom.xml-AUX
compareFiles pom.xml "<version>" $VERSION
fi
# -------------
# Bump Maven project property
# -------------
if [[ "${BUMP_MAVEN_PROPERTY_VERSION}" == true ]]; then
mvn --batch-mode \
-DskipTests=true \
versions:set-property \
-Dproperty="${PROPERTY}" \
-DnewVersion="${VERSION}"
fi
# -------------
# Bump Maven dependency property
# -------------
if [[ "${BUMP_MAVEN_DEPENDENCY_VERSION}" == true ]]; then
mvn --batch-mode versions:use-dep-version -Dincludes=$DEPENDENCY -DdepVersion=$VERSION -DforceVersion=true
fi
# -------------
# Bump docker-compose.yml service version
# -------------
if [[ "${BUMP_DOCKER_COMPOSE_SERVICE_VERSION}" == true ]]; then
sed -r "s|image:\s+${SERVICE_IMAGE}:[[:alnum:]._-]+|image: ${SERVICE_IMAGE}:${VERSION}|g" ${DOCKER_COMPOSE_FILE} >${DOCKER_COMPOSE_FILE}-AUX
compareFiles $DOCKER_COMPOSE_FILE $SERVICE_IMAGE $VERSION
fi
# -------------
# Bump docker-compose.yml header version
# -------------
if [[ "${BUMP_DOCKER_COMPOSE_HEADER_VERSION}" == true ]]; then
sed -r "s|#\s+${HEADER}:\s+[[:alnum:]._-]+|# ${HEADER}: ${VERSION}|g" ${DOCKER_COMPOSE_FILE} >${DOCKER_COMPOSE_FILE}-AUX
compareFiles $DOCKER_COMPOSE_FILE $HEADER $VERSION
fi
# -------------
# Bump Docker image version in files
# -------------
if [[ "${BUMP_DOCKER_IMAGE_VERSION_IN_FILES}" == true ]]; then
find . -type f -name ${FILE_NAME_PATTERN} | xargs sed -i -r "s|${IMAGE}:[[:alnum:]._-]+|${IMAGE}:${VERSION}|g"
fi
# -------------
# Bump application.properties variable value
# -------------
if [[ "${BUMP_APPLICATION_PROPERTIES_VAR_VALUE}" == true ]]; then
sed -r "s%${VARIABLE}((:|=)\s*).*$%${VARIABLE}\1${VALUE}%g" ${APPLICATION_PROPERTIES_FILE} >${APPLICATION_PROPERTIES_FILE}-AUX
compareFiles $APPLICATION_PROPERTIES_FILE $VARIABLE $VALUE
fi
# -------------
# Wait for NPM dependency to be available
# -------------
if [[ "${WAIT_FOR_NPM_DEPENDENCY}" == true ]]; then
CHECK_VERSION_AVAILABILTY="npm show ${DEPENDENCY}@${VERSION} version || echo ''"
VERSION_AUX=$(eval "${CHECK_VERSION_AVAILABILTY}")
until [[ "${VERSION_AUX}" == "${VERSION}" ]]; do
echo "Waiting for ${DEPENDENCY}@${VERSION} to be available in NPM..."
sleep 2
VERSION_AUX=$(eval "${CHECK_VERSION_AVAILABILTY}")
done
echo "${DEPENDENCY}@${VERSION} already available in NPM"
fi
# -------------
# Generic sed replacement
# -------------
if [[ "${GENERIC_SED}" == true ]]; then
sed -r "$SED_EXPRESSION" ${FILE} >${FILE}-AUX
compareFiles $FILE "(generic sed)" "$SED_EXPRESSION"
fi

View File

@ -1,161 +0,0 @@
#!/bin/bash -x
set -eu -o pipefail
############################################################################
# Any function offered by this file that is not path agnostic assumes that #
# the path is located where the first command of each function requires it #
############################################################################
# CI flags
PREPARE_TEST_ENVIRONMENT=false
USE_SPECIFIC_KURENTO_JAVA_COMMIT=false
SERVE_OV_TESTAPP=false
CHECK_AND_PREPARE_KURENTO_SNAPSHOT=false
if [[ -n ${1:-} ]]; then
case "${1:-}" in
--prepare-test-environment)
PREPARE_TEST_ENVIRONMENT=true
;;
--check-and-prepare-kurento-snapshot)
CHECK_AND_PREPARE_KURENTO_SNAPSHOT=true
;;
--use-specific-kurento-java-commit)
USE_SPECIFIC_KURENTO_JAVA_COMMIT=true
;;
--serve-openvidu-testapp)
SERVE_OV_TESTAPP=true
;;
*)
echo "Unrecognized method $1"
exit 1
;;
esac
else
echo "Must provide a method to execute as first parameter when calling the script"
exit 1
fi
# -------------
# Prepare build
# -------------
if [[ "${PREPARE_TEST_ENVIRONMENT}" == true ]]; then
# Connect e2e test container to network bridge so it is vissible for browser and media server containers
if [[ -n "${TEST_IMAGE}" ]]; then
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')" || echo "Docker container not found for image ${TEST_IMAGE}"
if [[ -n "${E2E_CONTAINER_ID}" ]]; then
docker network connect bridge "${E2E_CONTAINER_ID}"
else
echo "Could not connect test docker container to docker bridge, because no running container was found for image \"${TEST_IMAGE}\""
fi
else
echo "No TEST_IMAGE env var provided. Skipping network bridge connection"
fi
# Prepare directory for OpenVidu recordings
sudo mkdir -p /opt/openvidu/recordings && sudo chmod 777 /opt/openvidu/recordings
# Prepare directory for OpenVidu Android apps
sudo mkdir -p /opt/openvidu/android && sudo chmod 777 /opt/openvidu/android
# Download fake videos
FAKE_VIDEO1=/opt/openvidu/barcode.y4m
FAKE_VIDEO2=/opt/openvidu/girl.mjpeg
if [ ! -f ${FAKE_VIDEO1} ]; then
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/barcode.y4m --create-dirs --output /opt/openvidu/barcode.y4m
else
echo "File ${FAKE_VIDEO1} already exists"
fi
if [ ! -f ${FAKE_VIDEO2} ]; then
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/girl.mjpeg --create-dirs --output /opt/openvidu/girl.mjpeg
else
echo "File ${FAKE_VIDEO2} already exists"
fi
# Download fake audios
FAKE_AUDIO1=/opt/openvidu/fakeaudio.wav
FAKE_AUDIO2=/opt/openvidu/stt-test.wav
if [ ! -f ${FAKE_AUDIO1} ]; then
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/fakeaudio.wav --create-dirs --output /opt/openvidu/fakeaudio.wav
else
echo "File ${FAKE_AUDIO1} already exists"
fi
if [ ! -f ${FAKE_AUDIO2} ]; then
sudo curl --location https://github.com/OpenVidu/openvidu/raw/master/openvidu-test-e2e/docker/stt-test.wav --create-dirs --output /opt/openvidu/stt-test.wav
else
echo "File ${FAKE_AUDIO2} already exists"
fi
# Download recording custom layout
sudo curl --location https://raw.githubusercontent.com/OpenVidu/openvidu/master/openvidu-test-e2e/docker/my-custom-layout/index.html --create-dirs --output /opt/openvidu/test-layouts/layout1/index.html
# Open permissions for /opt/openvidu folder
sudo chmod -R 777 /opt/openvidu
# Pull browser images
# Pull chrome image if env variable CHROME_VERSION is set
if [[ -n "${CHROME_VERSION:-}" ]]; then
docker pull selenium/standalone-chrome:"${CHROME_VERSION}"
fi
# Pull firefox image if env variable FIREFOX_VERSION is set
if [[ -n "${FIREFOX_VERSION:-}" ]]; then
docker pull selenium/standalone-firefox:"${FIREFOX_VERSION}"
fi
# Pull edge image if env variable EDGE_VERSION is set
if [[ -n "${EDGE_VERSION:-}" ]]; then
docker pull selenium/standalone-edge:"${EDGE_VERSION}"
fi
# Pull Docker Android image if env variable DOCKER_ANDROID_IMAGE is set
if [[ -n "${DOCKER_ANDROID_IMAGE:-}" ]]; then
docker pull "${DOCKER_ANDROID_IMAGE}"
fi
# Pull mediasoup and kurento
if [[ -n "${MEDIASOUP_CONTROLLER_VERSION:-}" ]]; then
docker pull openvidu/mediasoup-controller:"${MEDIASOUP_CONTROLLER_VERSION}"
fi
if [[ -n "${KURENTO_MEDIA_SERVER_IMAGE:-}" ]]; then
docker pull "${KURENTO_MEDIA_SERVER_IMAGE}"
fi
fi
# -------------
# Use a specific kurento-java commit other than the configured in openvidu-parent pom.xml
# -------------
if [[ "${USE_SPECIFIC_KURENTO_JAVA_COMMIT}" == true ]]; then
git clone https://github.com/Kurento/kurento.git
pushd kurento/clients/java
git checkout -f "${KURENTO_JAVA_COMMIT}"
MVN_VERSION=$(mvn -q -Dexec.executable=echo -Dexec.args='${project.version}' --non-recursive exec:exec)
mvn -B -Dmaven.artifact.threads=1 clean install
popd
rm -rf kurento
mvn -B versions:set-property \
-Dproperty=version.kurento \
-DnewVersion="${MVN_VERSION}"
fi
# -------------
# Serve openvidu-testapp
# -------------
if [[ "${SERVE_OV_TESTAPP}" == true ]]; then
# Generate certificate
openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 \
-subj "/CN=www.mydom.com/O=My Company LTD./C=US" \
-keyout /opt/openvidu/testapp/key.pem \
-out /opt/openvidu/testapp/cert.pem
# Serve TestApp
pushd /opt/openvidu/testapp
http-server -S -p 4200 &>/opt/openvidu/testapp.log &
popd
fi

View File

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<pluginGroups></pluginGroups>
<proxies></proxies>
<servers></servers>
<mirrors></mirrors>
<profiles>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<repositories>
<repository>
<id>kurento-github-public</id>
<name>Kurento GitHub Maven packages (public access)</name>
<url>KURENTO_SNAPSHOTS_URL</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>kurento-github-public</id>
<name>Kurento GitHub Maven packages (public access)</name>
<url>KURENTO_SNAPSHOTS_URL</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
</settings>

View File

@ -1,241 +0,0 @@
#!/bin/bash -x
set -eu -o pipefail
############################################################################
# Any function offered by this file that is not path agnostic assumes that #
# the path is located where the first command of each function requires it #
############################################################################
OV_INTEGRATION_TESTS=false
OV_UNIT_TESTS=false
OV_E2E_KURENTO=false
OV_E2E_MEDIASOUP=false
LAUNCH_OV_KURENTO=false
LAUNCH_OV_MEDIASOUP=false
function environmentLaunch {
local MEDIA_SERVER="$1"
# Get e2e container id
local E2E_CONTAINER_ID
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')"
# Get e2e container IP so services running can be accessed by browser and media server containers
local E2E_CONTAINER_IP
E2E_CONTAINER_IP="$(docker inspect "$E2E_CONTAINER_ID" | awk '/bridge/,/IPAddress/' | grep IPAddress | cut -d'"' -f4)"
# Kurento and mediasoup needs to run as network host, so we need Docker host IP.
local DOCKER_HOST_IP
DOCKER_HOST_IP="$(docker inspect bridge --format '{{with index .IPAM.Config 0}}{{or .Gateway .Subnet}}{{end}}' | sed -r 's|\.0/[[:digit:]]+$|.1|')"
if [[ "${MEDIA_SERVER}" == "kurento" ]]; then
docker run -e KMS_UID=$(id -u) --network=host --detach=true --volume=/opt/openvidu/recordings:/opt/openvidu/recordings "${KURENTO_MEDIA_SERVER_IMAGE}"
while true; do
RC="$(curl \
--silent \
--no-buffer \
--write-out '%{http_code}' \
--header "Connection: Upgrade" \
--header "Upgrade: websocket" \
--header "Host: ${DOCKER_HOST_IP}" \
--header "Origin: ${DOCKER_HOST_IP}" \
"http://${DOCKER_HOST_IP}:8888/kurento" || echo '')"
if [[ "$RC" == "500" ]]; then
break
else
echo "Waiting for ${MEDIA_SERVER}..."
sleep 1
fi
done
elif [[ "${MEDIA_SERVER}" == "mediasoup" ]]; then
LOG_DATE=$(printf '%(%Y-%m-%d-%H-%M-%S)T')
docker run --network=host --restart=always \
--env=KMS_MIN_PORT=40000 \
--env=KMS_MAX_PORT=65535 \
--env=OPENVIDU_PRO_LICENSE="${OPENVIDU_PRO_LICENSE}" \
--env=OPENVIDU_PRO_LICENSE_API="${OPENVIDU_PRO_LICENSE_API}" \
--env=WEBRTC_LISTENIPS_0_ANNOUNCEDIP="${DOCKER_HOST_IP}" \
--env=WEBRTC_LISTENIPS_0_IP="${DOCKER_HOST_IP}" \
--volume=/opt/openvidu/recordings:/opt/openvidu/recordings \
openvidu/mediasoup-controller:"${MEDIASOUP_CONTROLLER_VERSION}" >& /opt/openvidu/mediasoup-controller-${LOG_DATE}.log &
until $(curl --insecure --output /dev/null --silent http://${DOCKER_HOST_IP}:8888/kurento); do
echo "Waiting for ${MEDIA_SERVER}..."
sleep 1
done
else
echo "Not valid media server"
exit 1
fi
if [ "${DOCKER_RECORDING_VERSION}" != "default" ]; then
echo "Using custom openvidu-recording tag: ${DOCKER_RECORDING_VERSION}"
java -jar -DKMS_URIS="[\"ws://${DOCKER_HOST_IP}:8888/kurento\"]" \
-DDOMAIN_OR_PUBLIC_IP="${E2E_CONTAINER_IP}" \
-DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true \
-DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts \
-DOPENVIDU_RECORDING_VERSION="${DOCKER_RECORDING_VERSION}" -DOPENVIDU_WEBHOOK=true \
-DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook \
/opt/openvidu/openvidu-server-*.jar &>/opt/openvidu/openvidu-server-"${MEDIA_SERVER}".log &
else
echo "Using default openvidu-recording tag"
java -jar -DKMS_URIS="[\"ws://${DOCKER_HOST_IP}:8888/kurento\"]" \
-DDOMAIN_OR_PUBLIC_IP="${E2E_CONTAINER_IP}" \
-DOPENVIDU_SECRET=MY_SECRET -DHTTPS_PORT=4443 -DOPENVIDU_RECORDING=true \
-DOPENVIDU_RECORDING_CUSTOM_LAYOUT=/opt/openvidu/test-layouts -DOPENVIDU_WEBHOOK=true \
-DOPENVIDU_WEBHOOK_ENDPOINT=http://127.0.0.1:7777/webhook \
/opt/openvidu/openvidu-server-*.jar &>/opt/openvidu/openvidu-server-"${MEDIA_SERVER}".log &
fi
until $(curl --insecure --output /dev/null --silent --head --fail https://OPENVIDUAPP:MY_SECRET@localhost:4443/); do
echo "Waiting for openvidu-server..."
sleep 2
done
}
function openviduE2ETests {
local MEDIA_SERVER="$1"
# Get e2e container id
local E2E_CONTAINER_ID
E2E_CONTAINER_ID="$(docker ps | grep "$TEST_IMAGE" | awk '{ print $1 }')"
# Get e2e container IP so services running can be accessed by browser and media server containers
local E2E_CONTAINER_IP
E2E_CONTAINER_IP="$(docker inspect "$E2E_CONTAINER_ID" | awk '/bridge/,/IPAddress/' | grep IPAddress | cut -d'"' -f4)"
# Kurento and mediasoup needs to run as network host, so we need Docker host IP.
local DOCKER_HOST_IP
DOCKER_HOST_IP="$(docker network inspect bridge | grep Subnet | cut -d'"' -f4 | cut -d'/' -f1 | sed 's/.$/1/' | grep 172)"
pushd openvidu-test-e2e
if [[ "${MEDIA_SERVER}" == "kurento" ]]; then
mvn -DMEDIA_SERVER_IMAGE="${KURENTO_MEDIA_SERVER_IMAGE}" \
-DOPENVIDU_URL="https://${E2E_CONTAINER_IP}:4443" \
-DCHROME_VERSION="${CHROME_VERSION}" \
-DFIREFOX_VERSION="${FIREFOX_VERSION}" \
-DEDGE_VERSION="${EDGE_VERSION}" \
-Dtest=OpenViduTestAppE2eTest \
-DAPP_URL="https://${E2E_CONTAINER_IP}:4200" \
-DEXTERNAL_CUSTOM_LAYOUT_URL="http://${E2E_CONTAINER_IP}:4114" \
-DREMOTE_URL_CHROME="http://${DOCKER_HOST_IP}:6666/wd/hub/" \
-DREMOTE_URL_FIREFOX="http://${DOCKER_HOST_IP}:6667/wd/hub/" \
-DREMOTE_URL_OPERA="http://${DOCKER_HOST_IP}:6668/wd/hub/" \
-DREMOTE_URL_EDGE="http://${DOCKER_HOST_IP}:6669/wd/hub/" \
-DEXTERNAL_CUSTOM_LAYOUT_PARAMS="sessionId,CUSTOM_LAYOUT_SESSION,secret,MY_SECRET" \
test
elif [[ "${MEDIA_SERVER}" == "mediasoup" ]]; then
mvn -DMEDIA_SERVER_IMAGE="openvidu/mediasoup-controller:${MEDIASOUP_CONTROLLER_VERSION}" \
-DOPENVIDU_URL="https://${E2E_CONTAINER_IP}:4443" \
-DCHROME_VERSION="${CHROME_VERSION}" \
-DFIREFOX_VERSION="${FIREFOX_VERSION}" \
-DEDGE_VERSION="${EDGE_VERSION}" \
-Dtest=OpenViduTestAppE2eTest \
-DAPP_URL="https://${E2E_CONTAINER_IP}:4200" \
-DEXTERNAL_CUSTOM_LAYOUT_URL="http://${E2E_CONTAINER_IP}:4114" \
-DREMOTE_URL_CHROME="http://${DOCKER_HOST_IP}:6666/wd/hub/" \
-DREMOTE_URL_FIREFOX="http://${DOCKER_HOST_IP}:6667/wd/hub/" \
-DREMOTE_URL_OPERA="http://${DOCKER_HOST_IP}:6668/wd/hub/" \
-DREMOTE_URL_EDGE="http://${DOCKER_HOST_IP}:6669/wd/hub/" \
-DEXTERNAL_CUSTOM_LAYOUT_PARAMS="sessionId,CUSTOM_LAYOUT_SESSION,secret,MY_SECRET" \
-DOPENVIDU_PRO_LICENSE="${OPENVIDU_PRO_LICENSE}" \
-DOPENVIDU_PRO_LICENSE_API="${OPENVIDU_PRO_LICENSE_API}" \
test
else
echo "Not valid media server"
exit 1
fi
stopMediaServer
kill -9 $(pgrep -f /opt/openvidu/openvidu-server) || true
popd
}
function stopMediaServer {
# Remove Kurento Media Server
declare -a arr=("kurento/kurento-media-server"
"openvidu/mediasoup-controller:")
for image in "${arr[@]}"; do
docker ps -a | awk '{ print $1,$2 }' | grep "${image}" | awk '{ print $1 }' | xargs -I {} docker rm -f {} || true
done
docker ps -a
}
# Environment variables
if [[ -n ${1:-} ]]; then
case "${1:-}" in
--openvidu-server-unit-tests)
OV_UNIT_TESTS=true
;;
--openvidu-server-integration-tests)
OV_INTEGRATION_TESTS=true
;;
--openvidu-e2e-tests-kurento)
OV_E2E_KURENTO=true
;;
--openvidu-e2e-tests-mediasoup)
OV_E2E_MEDIASOUP=true
;;
--environment-launch-kurento)
LAUNCH_OV_KURENTO=true
;;
--environment-launch-mediasoup)
LAUNCH_OV_MEDIASOUP=true
;;
*)
echo "Unrecognized method $1"
exit 1
;;
esac
else
echo "Must provide a method to execute as first parameter when calling the script"
exit 1
fi
# -------------
# openvidu-server unit tests
# -------------
if [[ "${OV_UNIT_TESTS}" == true ]]; then
pushd openvidu-server
mvn -B -Dtest=io.openvidu.server.test.unit.*Test test
popd
fi
# -------------
# openvidu-server integration tests
# -------------
if [[ "${OV_INTEGRATION_TESTS}" == true ]]; then
pushd openvidu-server
mvn -B -Dtest=io.openvidu.server.test.integration.*Test test
popd
fi
# -------------
# OpenVidu E2E Tests Kurento
# -------------
if [[ "${OV_E2E_KURENTO}" == true ]]; then
openviduE2ETests "kurento"
fi
# -------------
# OpenVidu E2E Tests mediasoup
# -------------
if [[ "${OV_E2E_MEDIASOUP}" == true ]]; then
openviduE2ETests "mediasoup"
fi
# -------------
# Environment launch Kurento
# -------------
if [[ "${LAUNCH_OV_KURENTO}" == true ]]; then
environmentLaunch "kurento"
fi
# -------------
# Environment launch mediasoup
# -------------
if [[ "${LAUNCH_OV_MEDIASOUP}" == true ]]; then
environmentLaunch "mediasoup"
fi

View File

@ -1,62 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
docs/
lib/
static/**

View File

@ -1 +0,0 @@
docs/

View File

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

View File

@ -1,201 +0,0 @@
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

@ -1,7 +0,0 @@
#!/bin/bash
set -e
SEARCH_STRING_1="<K extends keyof SessionEventMap>(type: K,"
REPLACE_STRING_1="<K extends keyof SessionEventMap>(type: K | string,"
SEARCH_STRING_2='\[key: `signal:\${string}`\]: SignalEvent;'
sed -i "s~${SEARCH_STRING_1}~${REPLACE_STRING_1}~g" ts4.4/lib/OpenVidu/Session.d.ts
sed -i "/${SEARCH_STRING_2}/d" ts4.4/lib/OpenViduInternal/Events/EventMap/SessionEventMap.d.ts

View File

@ -1,20 +0,0 @@
{
"include": ["../src"],
"exclude": ["../config", "../docs", "../lib", "../node_modules", "../ts4.4"],
"typedocOptions": {
"name": "OpenVidu Browser",
"entryPoints": ["../src/index.ts"],
"out": "../docs",
"theme": "default",
"readme": "none",
"includeVersion": true,
"validation": {
"notExported": true,
"invalidLink": true
},
"excludeExternals": true,
"excludePrivate": true,
"excludeProtected": true,
"excludeInternal": true
}
}

View File

@ -1,154 +0,0 @@
{
"extends": "tslint:recommended",
"rules": {
"array-type": [
true,
"array"
],
"ban-types": {
"options": [
[
"Object",
"Avoid using the `Object` type. Did you mean `object`?"
],
[
"Function",
"Avoid using the `Function` type. Prefer a specific function type, like `() => void`, or use `ts.AnyFunction`."
],
[
"Boolean",
"Avoid using the `Boolean` type. Did you mean `boolean`?"
],
[
"Number",
"Avoid using the `Number` type. Did you mean `number`?"
],
[
"String",
"Avoid using the `String` type. Did you mean `string`?"
]
]
},
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": [
true,
"ignore-same-line"
],
"indent": [
true,
"spaces",
2
],
"interface-name": [
true,
"never-prefix"
],
"interface-over-type-literal": true,
"jsdoc-format": true,
"no-inferrable-types": true,
"no-internal-module": true,
"no-null-keyword": false,
"no-switch-case-fall-through": true,
"no-trailing-whitespace": [
true,
"ignore-template-strings"
],
"no-var-keyword": true,
"object-literal-shorthand": true,
"one-line": [
true,
"check-open-brace",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single",
"avoid-escape",
"avoid-template"
],
"semicolon": [
true,
"always",
"ignore-bound-class-methods"
],
"space-within-parens": true,
"triple-equals": true,
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-type"
],
"no-implicit-dependencies": [
true,
"dev"
],
"object-literal-key-quotes": [
true,
"consistent-as-needed"
],
"variable-name": [
true,
"ban-keywords",
"check-format",
"allow-leading-underscore"
],
"arrow-parens": false,
"arrow-return-shorthand": false,
"forin": false,
"member-access": false,
"no-conditional-assignment": false,
"no-console": false,
"no-debugger": false,
"no-empty-interface": false,
"no-eval": false,
"no-object-literal-type-assertion": false,
"no-shadowed-variable": false,
"no-submodule-imports": false,
"no-var-requires": false,
"ordered-imports": false,
"prefer-conditional-expression": false,
"radix": false,
"trailing-comma": false,
"align": false,
"eofline": false,
"max-line-length": false,
"no-consecutive-blank-lines": false,
"space-before-function-paren": false,
"ban-comma-operator": false,
"max-classes-per-file": false,
"member-ordering": false,
"no-angle-bracket-type-assertion": false,
"no-bitwise": false,
"no-namespace": false,
"no-reference": false,
"object-literal-sort-keys": false,
"one-variable-per-declaration": false,
"unified-signatures": false
}
}

View File

@ -1,19 +0,0 @@
#!/bin/bash
if [[ -z "$BASEHREF_VERSION" ]]; then
echo "Example of use: \"BASEHREF_VERSION=2.12.0 ${0}\"" 1>&2
exit 1
fi
# Replace version from "stable" to the specified one in all TypeDoc links
grep -rl '/en/stable/' src | xargs sed -i -e 's|/en/stable/|/en/'${BASEHREF_VERSION}'/|g'
# Generate TypeDoc
./node_modules/typedoc/bin/typedoc --tsconfig ./config/tsconfig.json
# Return links to "stable" version
grep -rl '/en/'${BASEHREF_VERSION}'/' src | xargs sed -i -e 's|/en/'${BASEHREF_VERSION}'/|/en/stable/|g'
# Clean previous docs from openvidu.io-docs repo and copy new ones
rm -rf ../../openvidu.io-docs/docs/api/openvidu-browser/*
cp -R ./docs/. ../../openvidu.io-docs/docs/api/openvidu-browser

File diff suppressed because it is too large Load Diff

View File

@ -1,48 +0,0 @@
{
"author": "OpenVidu",
"dependencies": {
"events": "3.3.0",
"freeice": "2.2.2",
"hark": "1.2.3",
"inherits": "2.0.4",
"jsnlog": "2.30.0",
"mime": "3.0.0",
"platform": "1.3.6",
"semver": "7.6.2",
"uuid": "9.0.1",
"wolfy87-eventemitter": "5.2.9"
},
"description": "OpenVidu Browser",
"devDependencies": {
"@types/node": "18.11.9",
"@types/platform": "1.3.4",
"browserify": "17.0.0",
"terser": "5.15.1",
"tsify": "5.0.4",
"tslint": "6.1.3",
"typedoc": "0.23.21",
"typescript": "4.9.3"
},
"license": "Apache-2.0",
"main": "lib/index.js",
"name": "openvidu-browser",
"repository": {
"type": "git",
"url": "git://github.com/OpenVidu/openvidu"
},
"scripts": {
"browserify": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js Main.ts -p [ tsify ] --exclude kurento-browser-extensions --debug -o ../static/js/openvidu-browser-$VERSION.js -v",
"browserify-prod": "VERSION=${VERSION:-dev}; mkdir -p static/js/ && cd src && ../node_modules/browserify/bin/cmd.js --debug Main.ts -p [ tsify ] --exclude kurento-browser-extensions | ../node_modules/terser/bin/terser --source-map content=inline --output ../static/js/openvidu-browser-$VERSION.min.js",
"build": "cd src/OpenVidu && ./../../node_modules/typescript/bin/tsc && cd ../.. && ./node_modules/typescript/bin/tsc --declaration src/index.ts --outDir ./lib --sourceMap --target es5 --lib dom,es5,es2015.promise,scripthost && rm -rf ./ts4.4 && mkdir -p ./ts4.4/lib && cp -r ./lib ./ts4.4 && find ./ts4.4/lib -type f ! -iname '*.d.ts' -delete && ./config/replace_for_ts44.sh",
"docs": "./generate-docs.sh"
},
"types": "lib/index.d.ts",
"typesVersions": {
"<4.4": {
"*": [
"ts4.4/*"
]
}
},
"version": "2.30.0"
}

View File

@ -1,9 +0,0 @@
import { OpenVidu } from './OpenVidu/OpenVidu';
import { JL } from 'jsnlog';
if (typeof globalThis !== 'undefined') {
globalThis['OpenVidu'] = OpenVidu;
}
// Disable jsnlog when library is loaded
JL.setOptions({ enabled: false });

View File

@ -1,217 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Session } from './Session';
import { Stream } from './Stream';
import { LocalConnectionOptions } from '../OpenViduInternal/Interfaces/Private/LocalConnectionOptions';
import { RemoteConnectionOptions } from '../OpenViduInternal/Interfaces/Private/RemoteConnectionOptions';
import { InboundStreamOptions } from '../OpenViduInternal/Interfaces/Private/InboundStreamOptions';
import { StreamOptionsServer } from '../OpenViduInternal/Interfaces/Private/StreamOptionsServer';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* Represents each one of the user's connection to the session (the local one and other user's connections).
* Therefore each {@link Session} and {@link Stream} object has an attribute of type Connection
*/
export class Connection {
/**
* Unique identifier of the connection
*/
connectionId: string;
/**
* Time when this connection was created in OpenVidu Server (UTC milliseconds)
*/
creationTime: number;
/**
* Data associated to this connection (and therefore to certain user). This is an important field:
* it allows you to broadcast all the information you want for each user (a username, for example)
*/
data: string;
/**
* Role of the connection.
* - `SUBSCRIBER`: can subscribe to published Streams of other users by calling {@link Session.subscribe}
* - `PUBLISHER`: SUBSCRIBER permissions + can publish their own Streams by calling {@link Session.publish}
* - `MODERATOR`: SUBSCRIBER + PUBLISHER permissions + can force the unpublishing or disconnection over a third-party Stream or Connection by call {@link Session.forceUnpublish} and {@link Session.forceDisconnect}
*
* **Only defined for the local connection. In remote connections will be `undefined`**
*/
role: string;
/**
* Whether the streams published by this Connection will be recorded or not. This only affects [INDIVIDUAL recording](/en/stable/advanced-features/recording/#individual-recording-selection) <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin-right: 5px; border-radius: 3px; font-size: 13px; line-height:21px; font-family: Montserrat, sans-serif">PRO</a>
*
* **Only defined for the local connection. In remote connections will be `undefined`**
*/
record: boolean;
/**
* @hidden
*/
stream?: Stream;
/**
* @hidden
*/
localOptions: LocalConnectionOptions | undefined;
/**
* @hidden
*/
remoteOptions: RemoteConnectionOptions | undefined;
/**
* @hidden
*/
disposed = false;
/**
* @hidden
*/
rpcSessionId: string;
/**
* @hidden
*/
constructor(private session: Session, connectionOptions: LocalConnectionOptions | RemoteConnectionOptions) {
let msg = "'Connection' created ";
if (!!(<LocalConnectionOptions>connectionOptions).role) {
// Connection is local
this.localOptions = <LocalConnectionOptions>connectionOptions;
this.connectionId = this.localOptions.id;
this.creationTime = this.localOptions.createdAt;
this.data = this.localOptions.metadata;
this.rpcSessionId = this.localOptions.sessionId;
this.role = this.localOptions.role;
this.record = this.localOptions.record;
msg += '(local)';
} else {
// Connection is remote
this.remoteOptions = <RemoteConnectionOptions>connectionOptions;
this.connectionId = this.remoteOptions.id;
this.creationTime = this.remoteOptions.createdAt;
if (this.remoteOptions.metadata) {
this.data = this.remoteOptions.metadata;
}
if (this.remoteOptions.streams) {
this.initRemoteStreams(this.remoteOptions.streams);
}
msg += "(remote) with 'connectionId' [" + this.remoteOptions.id + ']';
}
logger.info(msg);
}
/* Hidden methods */
/**
* @hidden
*/
sendIceCandidate(candidate: RTCIceCandidate): void {
if (!this.disposed) {
logger.debug((!!this.stream!.outboundStreamOpts ? 'Local' : 'Remote') + 'candidate for' + this.connectionId, candidate);
this.session.openvidu.sendRequest(
'onIceCandidate',
{
endpointName: this.connectionId,
candidate: candidate.candidate,
sdpMid: candidate.sdpMid,
sdpMLineIndex: candidate.sdpMLineIndex
},
(error, response) => {
if (error) {
logger.error('Error sending ICE candidate: ' + JSON.stringify(error));
this.session.emitEvent('exception', [
new ExceptionEvent(
this.session,
ExceptionEventName.ICE_CANDIDATE_ERROR,
this.session,
'There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side',
error
)
]);
}
}
);
} else {
logger.warn(`Connection ${this.connectionId} disposed when trying to send an ICE candidate. ICE candidate not sent`);
}
}
/**
* @hidden
*/
initRemoteStreams(options: StreamOptionsServer[]): void {
// This is ready for supporting multiple streams per Connection object. Right now the loop will always run just once
// this.stream should also be replaced by a collection of streams to support multiple streams per Connection
options.forEach((opts) => {
const streamOptions: InboundStreamOptions = {
id: opts.id,
createdAt: opts.createdAt,
connection: this,
hasAudio: opts.hasAudio,
hasVideo: opts.hasVideo,
audioActive: opts.audioActive,
videoActive: opts.videoActive,
typeOfVideo: opts.typeOfVideo,
frameRate: opts.frameRate,
videoDimensions: !!opts.videoDimensions ? JSON.parse(opts.videoDimensions) : undefined,
filter: !!opts.filter ? opts.filter : undefined
};
const stream = new Stream(this.session, streamOptions);
this.addStream(stream);
});
logger.info(
"Remote 'Connection' with 'connectionId' [" + this.connectionId + '] is now configured for receiving Streams with options: ',
this.stream!.inboundStreamOpts
);
}
/**
* @hidden
*/
addStream(stream: Stream): void {
stream.connection = this;
this.stream = stream;
}
/**
* @hidden
*/
removeStream(): void {
delete this.stream;
}
/**
* @hidden
*/
dispose(): void {
this.disposed = true;
this.removeStream();
}
}

View File

@ -1,112 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from '../OpenViduInternal/Events/Event';
import { EventMap } from '../OpenViduInternal/Events/EventMap/EventMap';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import EventEmitter = require('wolfy87-eventemitter');
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
export abstract class EventDispatcher {
/**
* @hidden
*/
userHandlerArrowHandler: WeakMap<(event: Event) => void, (event: Event) => void> = new WeakMap();
/**
* @hidden
*/
ee = new EventEmitter();
/**
* Adds function `handler` to handle event `type`
*
* @returns The EventDispatcher object
*/
abstract on<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
/**
* Adds function `handler` to handle event `type` just once. The handler will be automatically removed after first execution
*
* @returns The object that dispatched the event
*/
abstract once<K extends keyof EventMap>(type: K, handler: (event: EventMap[K]) => void): this;
/**
* Removes a `handler` from event `type`. If no handler is provided, all handlers will be removed from the event
*
* @returns The object that dispatched the event
*/
abstract off<K extends keyof EventMap>(type: K, handler?: (event: EventMap[K]) => void): this;
/**
* @hidden
*/
onAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = (event) => {
if (event) {
logger.debug(message, event);
} else {
logger.debug(message);
}
handler(event);
};
this.userHandlerArrowHandler.set(handler, arrowHandler);
this.ee.on(type, arrowHandler);
return this;
}
/**
* @hidden
*/
onceAux(type: string, message: string, handler: (event: Event) => void): EventDispatcher {
const arrowHandler = (event) => {
if (event) {
logger.debug(message, event);
} else {
logger.debug(message);
}
handler(event);
// Remove handler from map after first and only execution
this.userHandlerArrowHandler.delete(handler);
};
this.userHandlerArrowHandler.set(handler, arrowHandler);
this.ee.once(type, arrowHandler);
return this;
}
/**
* @hidden
*/
offAux(type: string, handler?: (event: Event) => void): EventDispatcher {
if (!handler) {
this.ee.removeAllListeners(type);
} else {
// Must remove internal arrow function handler paired with user handler
const arrowHandler = this.userHandlerArrowHandler.get(handler);
if (!!arrowHandler) {
this.ee.off(type, arrowHandler);
}
this.userHandlerArrowHandler.delete(handler);
}
return this;
}
}

View File

@ -1,285 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Stream } from './Stream';
import { FilterEvent } from '../OpenViduInternal/Events/FilterEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* **WARNING**: experimental option. This interface may change in the near future
*
* Video/audio filter applied to a Stream. See {@link Stream.applyFilter}
*/
export class Filter {
/**
* Type of filter applied. This is the name of the remote class identifying the filter to apply in Kurento Media Server.
* For example: `"FaceOverlayFilter"`, `"GStreamerFilter"`.
*
* You can get this property in `*.kmd.json` files defining the Kurento filters. For example, for GStreamerFilter that's
* [here](https://github.com/Kurento/kms-filters/blob/53a452fac71d61795952e3d2202156c6b00f6d65/src/server/interface/filters.GStreamerFilter.kmd.json#L4)
*/
type: string;
/**
* Parameters used to initialize the filter.
* These correspond to the constructor parameters used in the filter in Kurento Media Server (except for `mediaPipeline` parameter, which is never needed).
*
* For example: for `filter.type = "GStreamerFilter"` could be `filter.options = {"command": "videobalance saturation=0.0"}`
*
* You can get this property in `*.kmd.json` files defining the Kurento filters. For example, for GStreamerFilter that's
* [here](https://github.com/Kurento/kms-filters/blob/53a452fac71d61795952e3d2202156c6b00f6d65/src/server/interface/filters.GStreamerFilter.kmd.json#L13-L31)
*/
options: Object;
/**
* Value passed the last time {@link Filter.execMethod} was called. If `undefined` this method has not been called yet.
*
* You can use this value to know the current status of any applied filter
*/
lastExecMethod?: {
method: string;
params: Object;
};
/**
* @hidden
*/
handlers: Map<string, (event: FilterEvent) => void> = new Map();
/**
* @hidden
*/
stream: Stream;
private logger: OpenViduLogger;
/**
* @hidden
*/
constructor(type: string, options: Object) {
this.type = type;
this.options = options;
}
/**
* Executes a filter method. Available methods are specific for each filter
*
* @param method Name of the method
* @param params Parameters of the method
*/
execMethod(method: string, params: Object): Promise<void> {
return new Promise((resolve, reject) => {
logger.info('Executing filter method to stream ' + this.stream.streamId);
let finalParams;
const successExecMethod = (triggerEvent) => {
logger.info('Filter method successfully executed on Stream ' + this.stream.streamId);
const oldValue = (<any>Object).assign({}, this.stream.filter);
this.stream.filter!.lastExecMethod = { method, params: finalParams };
if (triggerEvent) {
this.stream.session.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(
this.stream.session,
this.stream,
'filter',
this.stream.filter!,
oldValue,
'execFilterMethod'
)
]);
this.stream.streamManager.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(
this.stream.streamManager,
this.stream,
'filter',
this.stream.filter!,
oldValue,
'execFilterMethod'
)
]);
}
return resolve();
};
if (this.type.startsWith('VB:')) {
if (typeof params === 'string') {
try {
params = JSON.parse(params);
} catch (error) {
return reject(new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'Wrong params syntax: ' + error));
}
}
finalParams = params;
if (method === 'update') {
if (!this.stream.virtualBackgroundSinkElements?.VB) {
return reject(
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, 'There is no Virtual Background filter applied')
);
} else {
this.stream.virtualBackgroundSinkElements.VB.updateValues(params)
.then(() => successExecMethod(false))
.catch((error) => {
if (error.name === OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR) {
return reject(new OpenViduError(error.name, error.message));
} else {
return reject(
new OpenViduError(
OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR,
'Error updating values on Virtual Background filter: ' + error
)
);
}
});
}
} else {
return reject(
new OpenViduError(OpenViduErrorName.VIRTUAL_BACKGROUND_ERROR, `Unknown Virtual Background method "${method}"`)
);
}
} else {
let stringParams;
if (typeof params !== 'string') {
try {
stringParams = JSON.stringify(params);
} catch (error) {
const errorMsg = "'params' property must be a JSON formatted object";
logger.error(errorMsg);
return reject(errorMsg);
}
} else {
stringParams = <string>params;
}
finalParams = stringParams;
this.stream.session.openvidu.sendRequest(
'execFilterMethod',
{ streamId: this.stream.streamId, method, params: stringParams },
(error, response) => {
if (error) {
logger.error('Error executing filter method for Stream ' + this.stream.streamId, error);
if (error.code === 401) {
return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to execute a filter method"
)
);
} else {
return reject(error);
}
} else {
return successExecMethod(true);
}
}
);
}
});
}
/**
* Subscribe to certain filter event. Available events are specific for each filter
*
* @param eventType Event to which subscribe to.
* @param handler Function to execute upon event dispatched. It receives as parameter a {@link FilterEvent} object
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully attached to the filter and rejected with an Error object if not
*/
addEventListener(eventType: string, handler: (event: FilterEvent) => void): Promise<void> {
return new Promise((resolve, reject) => {
logger.info('Adding filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest(
'addFilterEventListener',
{ streamId: this.stream.streamId, eventType },
(error, response) => {
if (error) {
logger.error(
'Error adding filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
error
);
if (error.code === 401) {
return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to add a filter event listener"
)
);
} else {
return reject(error);
}
} else {
this.handlers.set(eventType, handler);
logger.info(
'Filter event listener to event ' + eventType + ' successfully applied on Stream ' + this.stream.streamId
);
return resolve();
}
}
);
});
}
/**
* Removes certain filter event listener previously set.
*
* @param eventType Event to unsubscribe from.
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the event listener was successfully removed from the filter and rejected with an Error object in other case
*/
removeEventListener(eventType: string): Promise<void> {
return new Promise((resolve, reject) => {
logger.info('Removing filter event listener to event ' + eventType + ' to stream ' + this.stream.streamId);
this.stream.session.openvidu.sendRequest(
'removeFilterEventListener',
{ streamId: this.stream.streamId, eventType },
(error, response) => {
if (error) {
logger.error(
'Error removing filter event listener to event ' + eventType + 'for Stream ' + this.stream.streamId,
error
);
if (error.code === 401) {
return reject(
new OpenViduError(
OpenViduErrorName.OPENVIDU_PERMISSION_DENIED,
"You don't have permissions to add a filter event listener"
)
);
} else {
return reject(error);
}
} else {
this.handlers.delete(eventType);
logger.info(
'Filter event listener to event ' + eventType + ' successfully removed on Stream ' + this.stream.streamId
);
return resolve();
}
}
);
});
}
}

View File

@ -1,416 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Stream } from './Stream';
import { LocalRecorderState } from '../OpenViduInternal/Enums/LocalRecorderState';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import Mime = require('mime/lite');
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
let platform: PlatformUtils;
/**
* Easy recording of {@link Stream} objects straightaway from the browser. Initialized with {@link OpenVidu.initLocalRecorder} method
*/
export class LocalRecorder {
state: LocalRecorderState;
private connectionId: string;
private mediaRecorder: MediaRecorder;
private chunks: any[] = [];
private blob?: Blob;
private id: string;
private videoPreviewSrc: string;
private videoPreview: HTMLVideoElement;
/**
* @hidden
*/
constructor(private stream: Stream) {
platform = PlatformUtils.getInstance();
this.connectionId = !!this.stream.connection ? this.stream.connection.connectionId : 'default-connection';
this.id = this.stream.streamId + '_' + this.connectionId + '_localrecord';
this.state = LocalRecorderState.READY;
}
/**
* Starts the recording of the Stream. {@link state} property must be `READY`. After method succeeds is set to `RECORDING`
*
* @param options The [MediaRecorder.options](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder#parameters) to be used to record this Stream.
* For example:
*
* ```javascript
* var OV = new OpenVidu();
* var publisher = await OV.initPublisherAsync();
* var localRecorder = OV.initLocalRecorder(publisher.stream);
* var options = {
* mimeType: 'video/webm;codecs=vp8',
* audioBitsPerSecond:128000,
* videoBitsPerSecond:2500000
* };
* localRecorder.record(options);
* ```
*
* If not specified, the default options preferred by the platform will be used.
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully started and rejected with an Error object if not
*/
record(options?: any): Promise<void> {
return new Promise((resolve, reject) => {
try {
if (typeof options === 'string' || options instanceof String) {
return reject(
`When calling LocalRecorder.record(options) parameter 'options' cannot be a string. Must be an object like { mimeType: "${options}" }`
);
}
if (typeof MediaRecorder === 'undefined') {
logger.error(
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
);
throw Error(
'MediaRecorder not supported on your device. See compatibility in https://caniuse.com/#search=MediaRecorder'
);
}
if (this.state !== LocalRecorderState.READY) {
throw Error(
"'LocalRecord.record()' needs 'LocalRecord.state' to be 'READY' (current value: '" +
this.state +
"'). Call 'LocalRecorder.clean()' or init a new LocalRecorder before"
);
}
logger.log("Starting local recording of stream '" + this.stream.streamId + "' of connection '" + this.connectionId + "'");
if (!options) {
options = { mimeType: 'video/webm' };
} else if (!options.mimeType) {
options.mimeType = 'video/webm';
}
this.mediaRecorder = new MediaRecorder(this.stream.getMediaStream(), options);
this.mediaRecorder.start();
} catch (err) {
return reject(err);
}
this.mediaRecorder.ondataavailable = (e) => {
if (e.data.size > 0) {
this.chunks.push(e.data);
}
};
this.mediaRecorder.onerror = (e) => {
logger.error('MediaRecorder error: ', e);
};
this.mediaRecorder.onstart = () => {
logger.log('MediaRecorder started (state=' + this.mediaRecorder.state + ')');
};
this.mediaRecorder.onstop = () => {
this.onStopDefault();
};
this.mediaRecorder.onpause = () => {
logger.log('MediaRecorder paused (state=' + this.mediaRecorder.state + ')');
};
this.mediaRecorder.onresume = () => {
logger.log('MediaRecorder resumed (state=' + this.mediaRecorder.state + ')');
};
this.state = LocalRecorderState.RECORDING;
return resolve();
});
}
/**
* Ends the recording of the Stream. {@link state} property must be `RECORDING` or `PAUSED`. After method succeeds is set to `FINISHED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording successfully stopped and rejected with an Error object if not
*/
stop(): Promise<void> {
return new Promise((resolve, reject) => {
try {
if (this.state === LocalRecorderState.READY || this.state === LocalRecorderState.FINISHED) {
throw Error(
"'LocalRecord.stop()' needs 'LocalRecord.state' to be 'RECORDING' or 'PAUSED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.start()' before"
);
}
this.mediaRecorder.onstop = () => {
this.onStopDefault();
return resolve();
};
this.mediaRecorder.stop();
} catch (e) {
return reject(e);
}
});
}
/**
* Pauses the recording of the Stream. {@link state} property must be `RECORDING`. After method succeeds is set to `PAUSED`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully paused and rejected with an Error object if not
*/
pause(): Promise<void> {
return new Promise((resolve, reject) => {
try {
if (this.state !== LocalRecorderState.RECORDING) {
return reject(
Error(
"'LocalRecord.pause()' needs 'LocalRecord.state' to be 'RECORDING' (current value: '" +
this.state +
"'). Call 'LocalRecorder.start()' or 'LocalRecorder.resume()' before"
)
);
}
this.mediaRecorder.pause();
this.state = LocalRecorderState.PAUSED;
return resolve();
} catch (error) {
return reject(error);
}
});
}
/**
* Resumes the recording of the Stream. {@link state} property must be `PAUSED`. After method succeeds is set to `RECORDING`
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the recording was successfully resumed and rejected with an Error object if not
*/
resume(): Promise<void> {
return new Promise((resolve, reject) => {
try {
if (this.state !== LocalRecorderState.PAUSED) {
throw Error(
"'LocalRecord.resume()' needs 'LocalRecord.state' to be 'PAUSED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.pause()' before"
);
}
this.mediaRecorder.resume();
this.state = LocalRecorderState.RECORDING;
return resolve();
} catch (error) {
return reject(error);
}
});
}
/**
* Previews the recording, appending a new HTMLVideoElement to element with id `parentId`. {@link state} property must be `FINISHED`
*/
preview(parentElement): HTMLVideoElement {
if (this.state !== LocalRecorderState.FINISHED) {
throw Error(
"'LocalRecord.preview()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
);
}
this.videoPreview = document.createElement('video');
this.videoPreview.id = this.id;
this.videoPreview.autoplay = true;
if (platform.isSafariBrowser()) {
this.videoPreview.playsInline = true;
}
if (typeof parentElement === 'string') {
const parentElementDom = document.getElementById(parentElement);
if (parentElementDom) {
this.videoPreview = parentElementDom.appendChild(this.videoPreview);
}
} else {
this.videoPreview = parentElement.appendChild(this.videoPreview);
}
this.videoPreview.src = this.videoPreviewSrc;
return this.videoPreview;
}
/**
* Gracefully stops and cleans the current recording (WARNING: it is completely dismissed). Sets {@link state} to `READY` so the recording can start again
*/
clean(): void {
const f = () => {
delete this.blob;
this.chunks = [];
this.state = LocalRecorderState.READY;
};
if (this.state === LocalRecorderState.RECORDING || this.state === LocalRecorderState.PAUSED) {
this.stop()
.then(() => f())
.catch(() => f());
} else {
f();
}
}
/**
* Downloads the recorded video through the browser. {@link state} property must be `FINISHED`
*/
download(): void {
if (this.state !== LocalRecorderState.FINISHED) {
throw Error(
"'LocalRecord.download()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
);
} else {
const a: HTMLAnchorElement = document.createElement('a');
a.style.display = 'none';
document.body.appendChild(a);
const url = globalThis.URL.createObjectURL(<any>this.blob);
a.href = url;
a.download = this.id + '.' + Mime.getExtension(this.blob!.type);
a.click();
globalThis.URL.revokeObjectURL(url);
document.body.removeChild(a);
}
}
/**
* Gets the raw Blob file. Methods preview, download, uploadAsBinary and uploadAsMultipartfile use this same file to perform their specific actions. {@link state} property must be `FINISHED`
*/
getBlob(): Blob {
if (this.state !== LocalRecorderState.FINISHED) {
throw Error("Call 'LocalRecord.stop()' before getting Blob file");
} else {
return this.blob!;
}
}
/**
* Uploads the recorded video as a binary file performing an HTTP/POST operation to URL `endpoint`. {@link state} property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ```
* var headers = {
* "Cookie": "$Version=1; Skin=new;",
* "Authorization":"Basic QWxhZGpbjpuIHNlctZQ=="
* }
* ```
* @returns A Promise (to which you can optionally subscribe to) that is resolved with the `http.responseText` from server if the operation was successful and rejected with the failed `http.status` if not
*/
uploadAsBinary(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) {
return reject(
Error(
"'LocalRecord.uploadAsBinary()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
)
);
} else {
const http = new XMLHttpRequest();
http.open('POST', endpoint, true);
if (typeof headers === 'object') {
for (const key of Object.keys(headers)) {
http.setRequestHeader(key, headers[key]);
}
}
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status.toString().charAt(0) === '2') {
// Success response from server (HTTP status standard: 2XX is success)
return resolve(http.responseText);
} else {
return reject(http.status);
}
}
};
http.send(this.blob);
}
});
}
/**
* Uploads the recorded video as a multipart file performing an HTTP/POST operation to URL `endpoint`. {@link state} property must be `FINISHED`. Optional HTTP headers can be passed as second parameter. For example:
* ```
* var headers = {
* "Cookie": "$Version=1; Skin=new;",
* "Authorization":"Basic QWxhZGpbjpuIHNlctZQ=="
* }
* ```
* @returns A Promise (to which you can optionally subscribe to) that is resolved with the `http.responseText` from server if the operation was successful and rejected with the failed `http.status` if not:
*/
uploadAsMultipartfile(endpoint: string, headers?: any): Promise<any> {
return new Promise((resolve, reject) => {
if (this.state !== LocalRecorderState.FINISHED) {
return reject(
Error(
"'LocalRecord.uploadAsMultipartfile()' needs 'LocalRecord.state' to be 'FINISHED' (current value: '" +
this.state +
"'). Call 'LocalRecorder.stop()' before"
)
);
} else {
const http = new XMLHttpRequest();
http.open('POST', endpoint, true);
if (typeof headers === 'object') {
for (const key of Object.keys(headers)) {
http.setRequestHeader(key, headers[key]);
}
}
const sendable = new FormData();
sendable.append('file', this.blob!, this.id + '.' + Mime.getExtension(this.blob!.type));
http.onreadystatechange = () => {
if (http.readyState === 4) {
if (http.status.toString().charAt(0) === '2') {
// Success response from server (HTTP status standard: 2XX is success)
return resolve(http.responseText);
} else {
return reject(http.status);
}
}
};
http.send(sendable);
}
});
}
/* Private methods */
private onStopDefault(): void {
logger.log('MediaRecorder stopped (state=' + this.mediaRecorder.state + ')');
this.blob = new Blob(this.chunks, { type: this.mediaRecorder.mimeType });
this.chunks = [];
this.videoPreviewSrc = globalThis.URL.createObjectURL(this.blob);
this.state = LocalRecorderState.FINISHED;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,875 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { OpenVidu } from './OpenVidu';
import { Session } from './Session';
import { Stream } from './Stream';
import { StreamManager } from './StreamManager';
import { PublisherProperties } from '../OpenViduInternal/Interfaces/Public/PublisherProperties';
import { PublisherEventMap } from '../OpenViduInternal/Events/EventMap/PublisherEventMap';
import { StreamEvent } from '../OpenViduInternal/Events/StreamEvent';
import { StreamPropertyChangedEvent } from '../OpenViduInternal/Events/StreamPropertyChangedEvent';
import { OpenViduError, OpenViduErrorName } from '../OpenViduInternal/Enums/OpenViduError';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
import { TypeOfVideo } from '../OpenViduInternal/Enums/TypeOfVideo';
import { StreamEventReason } from '../OpenViduInternal/Events/Types/Types';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
let platform: PlatformUtils;
/**
* Packs local media streams. Participants can publish it to a session. Initialized with {@link OpenVidu.initPublisher} method.
*
* See available event listeners at {@link PublisherEventMap}.
*/
export class Publisher extends StreamManager {
/**
* Whether the Publisher has been granted access to the requested input devices or not
*/
accessAllowed = false;
/**
* Whether you have called {@link Publisher.subscribeToRemote} with value `true` or `false` (*false* by default)
*/
isSubscribedToRemote = false;
/**
* The {@link Session} to which the Publisher belongs
*/
session: Session; // Initialized by Session.publish(Publisher)
private accessDenied = false;
protected properties: PublisherProperties;
private permissionDialogTimeout: NodeJS.Timer;
/**
* @hidden
*/
openvidu: OpenVidu;
/**
* @hidden
*/
videoReference: HTMLVideoElement;
/**
* @hidden
*/
screenShareResizeInterval: NodeJS.Timer;
/**
* @hidden
*/
constructor(targEl: string | HTMLElement | undefined, properties: PublisherProperties, openvidu: OpenVidu) {
super(
new Stream(!!openvidu.session ? openvidu.session : new Session(openvidu), {
publisherProperties: properties,
mediaConstraints: {}
}),
targEl
);
platform = PlatformUtils.getInstance();
this.properties = properties;
this.openvidu = openvidu;
this.stream.ee.on('local-stream-destroyed', (reason: StreamEventReason) => {
this.stream.isLocalStreamPublished = false;
const streamEvent = new StreamEvent(true, this, 'streamDestroyed', this.stream, reason);
this.emitEvent('streamDestroyed', [streamEvent]);
streamEvent.callDefaultBehavior();
});
}
/**
* Publish or unpublish the audio stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
*
* #### Events dispatched
*
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
*
* The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
* The {@link Publisher} object of the local participant will also dispatch the exact same event
*
* The {@link Session} object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"audioActive"` and `reason` set to `"publishAudio"`
* The respective {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
*
* See {@link StreamPropertyChangedEvent} to learn more.
*
* @param enabled `true` to publish the audio stream, `false` to unpublish it
*/
publishAudio(enabled: boolean): void {
if (this.stream.audioActive !== enabled) {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
affectedMediaStream.getAudioTracks().forEach((track) => {
track.enabled = enabled;
});
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'audioActive',
newValue: enabled,
reason: 'publishAudio'
},
(error, response) => {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this.session, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
]);
this.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this, this.stream, 'audioActive', enabled, !enabled, 'publishAudio')
]);
this.session.sendVideoData(this.stream.streamManager);
}
}
);
}
this.stream.audioActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its audio stream');
}
}
/**
* Publish or unpublish the video stream (if available). Calling this method twice in a row passing same `enabled` value will have no effect
*
* #### Events dispatched
*
* > _Only if `Session.publish(Publisher)` has been called for this Publisher_
*
* The {@link Session} object of the local participant will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
* The {@link Publisher} object of the local participant will also dispatch the exact same event
*
* The {@link Session} object of every other participant connected to the session will dispatch a `streamPropertyChanged` event with `changedProperty` set to `"videoActive"` and `reason` set to `"publishVideo"`
* The respective {@link Subscriber} object of every other participant receiving this Publisher's stream will also dispatch the exact same event
*
* See {@link StreamPropertyChangedEvent} to learn more.
*
* @param enabled `true` to publish the video stream, `false` to unpublish it
* @param resource
*
* If parameter **`enabled`** is `false`, this optional parameter is of type boolean. It can be set to `true` to forcibly free the hardware resource associated to the video track, or can be set to `false` to keep the access to the hardware resource.
* Not freeing the resource makes the operation much more efficient, but depending on the platform two side-effects can be introduced: the video device may not be accessible by other applications and the access light of
* webcams may remain on. This is platform-dependent: some browsers will not present the side-effects even when not freeing the resource.
*
* If parameter **`enabled`** is `true`, this optional parameter is of type [MediaStreamTrack](https://developer.mozilla.org/docs/Web/API/MediaStreamTrack). It can be set to force the restoration of the video track with a custom track. This may be
* useful if the Publisher was unpublished freeing the hardware resource, and openvidu-browser is not able to successfully re-create the video track as it was before unpublishing. In this way previous track settings will be ignored and this MediaStreamTrack
* will be used instead.
*/
publishVideo<T extends boolean>(enabled: T, resource?: T extends false ? boolean : MediaStreamTrack): Promise<void> {
return new Promise(async (resolve, reject) => {
if (this.stream.videoActive !== enabled) {
const affectedMediaStream: MediaStream = this.stream.displayMyRemote()
? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
let mustRestartMediaStream = false;
affectedMediaStream.getVideoTracks().forEach((track) => {
track.enabled = enabled;
if (!enabled && resource === true) {
track.stop();
} else if (enabled && track.readyState === 'ended') {
// Resource was freed
mustRestartMediaStream = true;
}
});
// There is a Virtual Background filter applied that must be removed in case the hardware must be freed
if (!enabled && resource === true && !!this.stream.filter && this.stream.filter.type.startsWith('VB:')) {
this.stream.lastVBFilter = this.stream.filter; // Save the filter to be re-applied in case of unmute
await this.stream.removeFilterAux(true);
}
if (mustRestartMediaStream) {
const oldVideoTrack = affectedMediaStream.getVideoTracks()[0];
affectedMediaStream.removeTrack(oldVideoTrack);
const replaceVideoTrack = async (tr: MediaStreamTrack) => {
affectedMediaStream.addTrack(tr);
if (this.stream.isLocalStreamPublished) {
await this.replaceTrackInRtcRtpSender(tr);
}
if (!!this.stream.lastVBFilter) {
setTimeout(async () => {
let options = this.stream.lastVBFilter!.options;
const lastExecMethod = this.stream.lastVBFilter!.lastExecMethod;
if (!!lastExecMethod && lastExecMethod.method === 'update') {
options = Object.assign({}, options, lastExecMethod.params);
}
await this.stream.applyFilter(this.stream.lastVBFilter!.type, options);
delete this.stream.lastVBFilter;
}, 1);
}
};
if (!!resource && resource instanceof MediaStreamTrack) {
await replaceVideoTrack(resource);
} else {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: this.stream.lastVideoTrackConstraints
});
await replaceVideoTrack(mediaStream.getVideoTracks()[0]);
} catch (error) {
return reject(error);
}
}
}
if (!!this.session && !!this.stream.streamId) {
this.session.openvidu.sendRequest(
'streamPropertyChanged',
{
streamId: this.stream.streamId,
property: 'videoActive',
newValue: enabled,
reason: 'publishVideo'
},
(error, response) => {
if (error) {
logger.error("Error sending 'streamPropertyChanged' event", error);
} else {
this.session.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(
this.session,
this.stream,
'videoActive',
enabled,
!enabled,
'publishVideo'
)
]);
this.emitEvent('streamPropertyChanged', [
new StreamPropertyChangedEvent(this, this.stream, 'videoActive', enabled, !enabled, 'publishVideo')
]);
this.session.sendVideoData(this.stream.streamManager);
}
}
);
}
this.stream.videoActive = enabled;
logger.info("'Publisher' has " + (enabled ? 'published' : 'unpublished') + ' its video stream');
return resolve();
}
});
}
/**
* Call this method before {@link Session.publish} if you prefer to subscribe to your Publisher's remote stream instead of using the local stream, as any other user would do.
*/
subscribeToRemote(value?: boolean): void {
value = value !== undefined ? value : true;
this.isSubscribedToRemote = value;
this.stream.subscribeToMyRemote(value);
}
/**
* See {@link EventDispatcher.on}
*/
on<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.on(<any>type, handler);
if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
} else {
this.stream.ee.on('stream-created-by-publisher', () => {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
});
}
}
if (type === 'accessAllowed') {
if (this.accessAllowed) {
this.emitEvent('accessAllowed', []);
}
}
if (type === 'accessDenied') {
if (this.accessDenied) {
this.emitEvent('accessDenied', []);
}
}
return this;
}
/**
* See {@link EventDispatcher.once}
*/
once<K extends keyof PublisherEventMap>(type: K, handler: (event: PublisherEventMap[K]) => void): this {
super.once(<any>type, handler);
if (type === 'streamCreated') {
if (!!this.stream && this.stream.isLocalStreamPublished) {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
} else {
this.stream.ee.once('stream-created-by-publisher', () => {
this.emitEvent('streamCreated', [new StreamEvent(false, this, 'streamCreated', this.stream, '')]);
});
}
}
if (type === 'accessAllowed') {
if (this.accessAllowed) {
this.emitEvent('accessAllowed', []);
}
}
if (type === 'accessDenied') {
if (this.accessDenied) {
this.emitEvent('accessDenied', []);
}
}
return this;
}
/**
* See {@link EventDispatcher.off}
*/
off<K extends keyof PublisherEventMap>(type: K, handler?: (event: PublisherEventMap[K]) => void): this {
super.off(<any>type, handler);
return this;
}
/**
* Replaces the current video or audio track with a different one. This allows you to replace an ongoing track with a different one
* without having to renegotiate the whole WebRTC connection (that is, initializing a new Publisher, unpublishing the previous one
* and publishing the new one).
*
* You can get this new MediaStreamTrack by using the native Web API or simply with {@link OpenVidu.getUserMedia} method.
*
* **WARNING: this method has been proven to work in the majority of cases, but there may be some combinations of published/replaced tracks that may be incompatible
* between them and break the connection in OpenVidu Server. A complete renegotiation may be the only solution in this case.
* Visit [RTCRtpSender.replaceTrack](https://developer.mozilla.org/en-US/docs/Web/API/RTCRtpSender/replaceTrack) documentation for further details.**
*
* @param track The [MediaStreamTrack](https://developer.mozilla.org/en-US/docs/Web/API/MediaStreamTrack) object to replace the current one.
* If it is an audio track, the current audio track will be the replaced one. If it is a video track, the current video track will be the replaced one.
*
* @returns A Promise (to which you can optionally subscribe to) that is resolved if the track was successfully replaced and rejected with an Error object in other case
*/
async replaceTrack(track: MediaStreamTrack): Promise<void> {
return this.replaceTrackAux(track, true);
}
/* Hidden methods */
/**
* @hidden
*/
initialize(): Promise<void> {
return new Promise(async (resolve, reject) => {
let constraints: MediaStreamConstraints = {};
let constraintsAux: MediaStreamConstraints = {};
const timeForDialogEvent = 2000;
let startTime;
const errorCallback = (openViduError: OpenViduError) => {
this.accessDenied = true;
this.accessAllowed = false;
logger.error(`Publisher initialization failed. ${openViduError.name}: ${openViduError.message}`);
return reject(openViduError);
};
const successCallback = (mediaStream: MediaStream) => {
this.accessAllowed = true;
this.accessDenied = false;
if (typeof MediaStreamTrack !== 'undefined' && this.properties.audioSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getAudioTracks()[0]);
mediaStream.addTrack(<MediaStreamTrack>this.properties.audioSource);
}
if (typeof MediaStreamTrack !== 'undefined' && this.properties.videoSource instanceof MediaStreamTrack) {
mediaStream.removeTrack(mediaStream.getVideoTracks()[0]);
mediaStream.addTrack(<MediaStreamTrack>this.properties.videoSource);
}
// Apply PublisherProperties.publishAudio and PublisherProperties.publishVideo
if (!!mediaStream.getAudioTracks()[0]) {
const enabled =
this.stream.audioActive !== undefined && this.stream.audioActive !== null
? this.stream.audioActive
: !!this.stream.outboundStreamOpts.publisherProperties.publishAudio;
mediaStream.getAudioTracks()[0].enabled = enabled;
}
if (!!mediaStream.getVideoTracks()[0]) {
const enabled =
this.stream.videoActive !== undefined && this.stream.videoActive !== null
? this.stream.videoActive
: !!this.stream.outboundStreamOpts.publisherProperties.publishVideo;
mediaStream.getVideoTracks()[0].enabled = enabled;
}
// Set Content Hint on all MediaStreamTracks
for (const track of mediaStream.getAudioTracks()) {
if (!track.contentHint?.length) {
// contentHint for audio: "", "speech", "speech-recognition", "music".
// https://w3c.github.io/mst-content-hint/#audio-content-hints
track.contentHint = '';
logger.info(`Audio track Content Hint set: '${track.contentHint}'`);
}
}
for (const track of mediaStream.getVideoTracks()) {
if (!track.contentHint?.length) {
// contentHint for video: "", "motion", "detail", "text".
// https://w3c.github.io/mst-content-hint/#video-content-hints
switch (this.stream.typeOfVideo) {
case TypeOfVideo.SCREEN:
track.contentHint = 'detail';
break;
case TypeOfVideo.CUSTOM:
logger.warn('CUSTOM type video track was provided without Content Hint!');
track.contentHint = 'motion';
break;
case TypeOfVideo.CAMERA:
case TypeOfVideo.IPCAM:
default:
track.contentHint = 'motion';
break;
}
logger.info(`Video track Content Hint set: '${track.contentHint}'`);
}
}
this.initializeVideoReference(mediaStream);
if (!this.stream.displayMyRemote()) {
// When we are subscribed to our remote we don't still set the MediaStream object in the video elements to
// avoid early 'streamPlaying' event
this.stream.updateMediaStreamInVideos();
}
delete this.firstVideoElement;
if (this.stream.isSendVideo()) {
// Has video track
this.getVideoDimensions().then((dimensions) => {
this.stream.videoDimensions = {
width: dimensions.width,
height: dimensions.height
};
if (this.stream.isSendScreen()) {
if(this.stream.isSendAudio() && mediaStream.getAudioTracks().length === 0){
// If sending audio is enabled and there are no audio tracks in the mediaStream, disable audio for screen sharing.
this.stream.audioActive = false;
this.stream.hasAudio = false;
this.stream.outboundStreamOpts.publisherProperties.publishAudio = false;
this.stream.outboundStreamOpts.publisherProperties.audioSource = false;
}
// Set interval to listen for screen resize events
this.screenShareResizeInterval = setInterval(() => {
const settings: MediaTrackSettings = mediaStream.getVideoTracks()[0].getSettings();
const newWidth = settings.width;
const newHeight = settings.height;
const widthChanged = newWidth != null && newWidth !== this.stream.videoDimensions.width;
const heightChanged = newHeight != null && newHeight !== this.stream.videoDimensions.height;
if (this.stream.isLocalStreamPublished && (widthChanged || heightChanged)) {
this.openvidu.sendVideoDimensionsChangedEvent(
this,
'screenResized',
this.stream.videoDimensions.width,
this.stream.videoDimensions.height,
newWidth || 0,
newHeight || 0
);
}
}, 650);
}
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
});
} else {
// Only audio track (no videoDimensions)
this.stream.isLocalStreamReadyToPublish = true;
this.stream.ee.emitEvent('stream-ready-to-publish', []);
}
return resolve();
};
const getMediaSuccess = async (mediaStream: MediaStream, definedAudioConstraint) => {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (this.stream.isSendScreen() && this.properties.audioSource !== 'screen' && this.stream.isSendAudio()) {
// When getting desktop as user media audio constraint must be false. Now we can ask for it if required
constraintsAux.audio = definedAudioConstraint;
constraintsAux.video = false;
startTime = Date.now();
this.setPermissionDialogTimer(timeForDialogEvent);
try {
const audioOnlyStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.addTrack(audioOnlyStream.getAudioTracks()[0]);
successCallback(mediaStream);
} catch (error) {
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
mediaStream.getAudioTracks().forEach((track) => {
track.stop();
});
mediaStream.getVideoTracks().forEach((track) => {
track.stop();
});
errorCallback(this.openvidu.generateAudioDeviceError(error, constraints));
return;
}
} else {
successCallback(mediaStream);
}
};
const getMediaError = async (error) => {
logger.error(`getMediaError: ${error.toString()}`);
this.clearPermissionDialogTimer(startTime, timeForDialogEvent);
if (error.name === 'Error') {
// Safari OverConstrainedError has as name property 'Error' instead of 'OverConstrainedError'
error.name = error.constructor.name;
}
let errorName, errorMessage;
switch (error.name.toLowerCase()) {
case 'notfounderror':
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: constraints.video
});
mediaStream.getVideoTracks().forEach((track) => {
track.stop();
});
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
} catch (error) {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
}
break;
case 'notallowederror':
errorName = this.stream.isSendScreen()
? OpenViduErrorName.SCREEN_CAPTURE_DENIED
: OpenViduErrorName.DEVICE_ACCESS_DENIED;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
case 'overconstrainederror':
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: constraints.video
});
mediaStream.getVideoTracks().forEach((track) => {
track.stop();
});
if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_AUDIO_DEVICE_NOT_FOUND;
errorMessage =
"Audio input device with deviceId '" +
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.audio).deviceId!!).exact +
"' not found";
} else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage =
"Audio input device doesn't support the value passed for constraint '" + error.constraint + "'";
}
errorCallback(new OpenViduError(errorName, errorMessage));
} catch (error) {
if (error.constraint.toLowerCase() === 'deviceid') {
errorName = OpenViduErrorName.INPUT_VIDEO_DEVICE_NOT_FOUND;
errorMessage =
"Video input device with deviceId '" +
(<ConstrainDOMStringParameters>(<MediaTrackConstraints>constraints.video).deviceId!!).exact +
"' not found";
} else {
errorName = OpenViduErrorName.PUBLISHER_PROPERTIES_ERROR;
errorMessage =
"Video input device doesn't support the value passed for constraint '" + error.constraint + "'";
}
errorCallback(new OpenViduError(errorName, errorMessage));
}
break;
case 'aborterror':
case 'notreadableerror':
errorName = OpenViduErrorName.DEVICE_ALREADY_IN_USE;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
default:
errorName = OpenViduErrorName.GENERIC_ERROR;
errorMessage = error.toString();
errorCallback(new OpenViduError(errorName, errorMessage));
break;
}
};
try {
const myConstraints = await this.openvidu.generateMediaConstraints(this.properties);
if (
(!!myConstraints.videoTrack && !!myConstraints.audioTrack) ||
(!!myConstraints.audioTrack && myConstraints.constraints?.video === false) ||
(!!myConstraints.videoTrack && myConstraints.constraints?.audio === false)
) {
// No need to call getUserMedia at all. MediaStreamTracks already provided
successCallback(this.openvidu.addAlreadyProvidedTracks(myConstraints, new MediaStream(), this.stream));
} else {
constraints = myConstraints.constraints;
const outboundStreamOptions = {
mediaConstraints: constraints,
publisherProperties: this.properties
};
this.stream.setOutboundStreamOptions(outboundStreamOptions);
const definedAudioConstraint = constraints.audio === undefined ? true : constraints.audio;
constraintsAux.audio = this.stream.isSendScreen() ? false : definedAudioConstraint;
constraintsAux.video = constraints.video;
startTime = Date.now();
this.setPermissionDialogTimer(timeForDialogEvent);
try {
if (this.stream.isSendScreen() && navigator.mediaDevices['getDisplayMedia'] && !platform.isElectron()) {
const mediaStream = await navigator.mediaDevices['getDisplayMedia']({ video: true, audio: this.properties.audioSource === 'screen' });
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream);
await getMediaSuccess(mediaStream, definedAudioConstraint);
} else {
this.stream.lastVideoTrackConstraints = constraintsAux.video;
const mediaStream = await navigator.mediaDevices.getUserMedia(constraintsAux);
this.openvidu.addAlreadyProvidedTracks(myConstraints, mediaStream, this.stream);
await getMediaSuccess(mediaStream, definedAudioConstraint);
}
} catch (error) {
await getMediaError(error);
}
}
} catch (error) {
errorCallback(error);
}
});
}
/**
* @hidden
*/
async replaceTrackAux(track: MediaStreamTrack, updateLastConstraints: boolean): Promise<void> {
// Set field "enabled" of the new track to the previous value
const trackOriginalEnabledValue: boolean = track.enabled;
if (track.kind === 'video') {
track.enabled = this.stream.videoActive;
} else if (track.kind === 'audio') {
track.enabled = this.stream.audioActive;
}
try {
if (this.stream.isLocalStreamPublished) {
// Only if the Publisher has been published is necessary to call native Web API RTCRtpSender.replaceTrack
// If it has not been published yet, replacing it on the MediaStream object is enough
this.replaceTrackInMediaStream(track, updateLastConstraints);
return await this.replaceTrackInRtcRtpSender(track);
} else {
// Publisher not published. Simply replace the track on the local MediaStream
return this.replaceTrackInMediaStream(track, updateLastConstraints);
}
} catch (error) {
track.enabled = trackOriginalEnabledValue;
throw error;
}
}
/**
* @hidden
*
* To obtain the videoDimensions we wait for the video reference to have enough metadata
* and then try to use MediaStreamTrack.getSettingsMethod(). If not available, then we
* use the HTMLVideoElement properties videoWidth and videoHeight
*/
getVideoDimensions(): Promise<{ width: number; height: number }> {
return new Promise((resolve, reject) => {
// Ionic iOS and Safari iOS supposedly require the video element to actually exist inside the DOM
const requiresDomInsertion: boolean = (platform.isIonicIos() || platform.isIOSWithSafari()) && (this.videoReference.readyState < 1);
let loadedmetadataListener;
const resolveDimensions = () => {
let width: number;
let height: number;
if (typeof this.stream.getMediaStream().getVideoTracks()[0].getSettings === 'function') {
const settings = this.stream.getMediaStream().getVideoTracks()[0].getSettings();
width = settings.width || this.videoReference.videoWidth;
height = settings.height || this.videoReference.videoHeight;
} else {
logger.warn('MediaStreamTrack does not have getSettings method on ' + platform.getDescription());
width = this.videoReference.videoWidth;
height = this.videoReference.videoHeight;
}
if (loadedmetadataListener != null) {
this.videoReference.removeEventListener('loadedmetadata', loadedmetadataListener);
}
if (requiresDomInsertion) {
document.body.removeChild(this.videoReference);
}
return resolve({ width, height });
};
if (this.videoReference.readyState >= 1) {
// The video already has metadata available
// No need of loadedmetadata event
resolveDimensions();
} else {
// The video does not have metadata available yet
// Must listen to loadedmetadata event
loadedmetadataListener = () => {
if (!this.videoReference.videoWidth) {
let interval = setInterval(() => {
if (!!this.videoReference.videoWidth) {
clearInterval(interval);
resolveDimensions();
}
}, 40);
} else {
resolveDimensions();
}
};
this.videoReference.addEventListener('loadedmetadata', loadedmetadataListener);
if (requiresDomInsertion) {
document.body.appendChild(this.videoReference);
}
}
});
}
/**
* @hidden
*/
reestablishStreamPlayingEvent() {
if (this.ee.getListeners('streamPlaying').length > 0) {
this.addPlayEventToFirstVideo();
}
}
/**
* @hidden
*/
initializeVideoReference(mediaStream: MediaStream) {
this.videoReference = document.createElement('video');
this.videoReference.style.display = 'none';
this.videoReference.muted = true;
this.videoReference.autoplay = true;
this.videoReference.controls = false;
if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
this.videoReference.playsInline = true;
}
this.stream.setMediaStream(mediaStream);
if (!!this.firstVideoElement) {
this.createVideoElement(this.firstVideoElement.targetElement, <VideoInsertMode>this.properties.insertMode);
}
this.videoReference.srcObject = this.stream.getMediaStream();
}
/**
* @hidden
*/
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
const mediaStream: MediaStream = this.stream.displayMyRemote()
? this.stream.localMediaStreamWhenSubscribedToRemote!
: this.stream.getMediaStream();
let removedTrack: MediaStreamTrack;
if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0];
if (updateLastConstraints) {
this.stream.lastVideoTrackConstraints = track.getConstraints();
}
} else {
removedTrack = mediaStream.getAudioTracks()[0];
}
removedTrack.enabled = false;
removedTrack.stop();
mediaStream.removeTrack(removedTrack);
mediaStream.addTrack(track);
const trackInfo = {
oldLabel: removedTrack?.label || '',
newLabel: track?.label || ''
};
if (track.kind === 'video' && updateLastConstraints) {
this.openvidu.sendNewVideoDimensionsIfRequired(this, 'trackReplaced', 50, 30);
this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'videoTrack');
if (this.stream.isLocalStreamPublished) {
this.session.sendVideoData(this.stream.streamManager, 5, true, 5);
}
} else if (track.kind === 'audio' && updateLastConstraints) {
this.openvidu.sendTrackChangedEvent(this, trackInfo.oldLabel, trackInfo.newLabel, 'audioTrack');
}
if (track.kind === 'audio') {
this.stream.disableHarkSpeakingEvent(false);
this.stream.disableHarkStoppedSpeakingEvent(false);
this.stream.disableHarkVolumeChangeEvent(false);
this.stream.initHarkEvents();
}
}
/* Private methods */
private setPermissionDialogTimer(waitTime: number): void {
this.permissionDialogTimeout = setTimeout(() => {
this.emitEvent('accessDialogOpened', []);
}, waitTime);
}
private clearPermissionDialogTimer(startTime: number, waitTime: number): void {
clearTimeout(this.permissionDialogTimeout);
if (Date.now() - startTime > waitTime) {
// Permission dialog was shown and now is closed
this.emitEvent('accessDialogClosed', []);
}
}
private async replaceTrackInRtcRtpSender(track: MediaStreamTrack): Promise<void> {
const senders: RTCRtpSender[] = this.stream.getRTCPeerConnection().getSenders();
let sender: RTCRtpSender | undefined;
if (track.kind === 'video') {
sender = senders.find((s) => !!s.track && s.track.kind === 'video');
if (!sender) {
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
}
} else if (track.kind === 'audio') {
sender = senders.find((s) => !!s.track && s.track.kind === 'audio');
if (!sender) {
throw new Error("There's no replaceable track for that kind of MediaStreamTrack in this Publisher object");
}
} else {
throw new Error('Unknown track kind ' + track.kind);
}
await (sender as RTCRtpSender).replaceTrack(track);
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,621 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Stream } from './Stream';
import { Subscriber } from './Subscriber';
import { EventDispatcher } from './EventDispatcher';
import { StreamManagerVideo } from '../OpenViduInternal/Interfaces/Public/StreamManagerVideo';
import { StreamManagerEventMap } from '../OpenViduInternal/Events/EventMap/StreamManagerEventMap';
import { StreamManagerEvent } from '../OpenViduInternal/Events/StreamManagerEvent';
import { VideoElementEvent } from '../OpenViduInternal/Events/VideoElementEvent';
import { ExceptionEvent, ExceptionEventName } from '../OpenViduInternal/Events/ExceptionEvent';
import { VideoInsertMode } from '../OpenViduInternal/Enums/VideoInsertMode';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
import { PlatformUtils } from '../OpenViduInternal/Utils/Platform';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
let platform: PlatformUtils;
/**
* Interface in charge of displaying the media streams in the HTML DOM. This wraps any {@link Publisher} and {@link Subscriber} object.
* You can insert as many video players fo the same Stream as you want by calling {@link StreamManager.addVideoElement} or
* {@link StreamManager.createVideoElement}.
* The use of StreamManager wrapper is particularly useful when you don't need to differentiate between Publisher or Subscriber streams or just
* want to directly manage your own video elements (even more than one video element per Stream). This scenario is pretty common in
* declarative, MVC frontend frameworks such as Angular, React or Vue.js
*
* See available event listeners at {@link StreamManagerEventMap}.
*/
export abstract class StreamManager extends EventDispatcher {
/**
* The Stream represented in the DOM by the Publisher/Subscriber
*/
stream: Stream;
/**
* All the videos displaying the Stream of this Publisher/Subscriber
*/
videos: StreamManagerVideo[] = [];
/**
* Whether the Stream represented in the DOM is local or remote
* - `false` for {@link Publisher}
* - `true` for {@link Subscriber}
*/
remote: boolean;
/**
* The DOM HTMLElement assigned as target element when creating the video for the Publisher/Subscriber. This property is only defined if:
* - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
* - {@link Subscriber} has been initialized by calling method {@link Session.subscribe} with a valid `targetElement` parameter
*/
targetElement: HTMLElement;
/**
* `id` attribute of the DOM video element displaying the Publisher/Subscriber's stream. This property is only defined if:
* - {@link Publisher} has been initialized by calling method {@link OpenVidu.initPublisher} with a valid `targetElement` parameter
* - {@link Subscriber} has been initialized by calling method {@link Session.subscribe} with a valid `targetElement` parameter
*/
id: string;
/**
* @hidden
*/
protected firstVideoElement?: StreamManagerVideo;
/**
* @hidden
*/
protected element: HTMLElement;
/**
* @hidden
*/
protected canPlayListener: EventListener;
/**
* @hidden
*/
private streamPlayingEventExceptionTimeout?: NodeJS.Timeout;
/**
* @hidden
*/
private lazyLaunchVideoElementCreatedEvent = false;
/**
* @hidden
*/
constructor(stream: Stream, targetElement?: HTMLElement | string) {
super();
platform = PlatformUtils.getInstance();
this.stream = stream;
this.stream.streamManager = this;
this.remote = !this.stream.isLocal();
if (!!targetElement) {
let targEl;
if (typeof targetElement === 'string') {
targEl = document.getElementById(targetElement);
} else if (targetElement instanceof HTMLElement) {
targEl = targetElement;
}
if (!!targEl) {
this.firstVideoElement = {
targetElement: targEl,
video: document.createElement('video'),
id: '',
canplayListenerAdded: false
};
if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
this.firstVideoElement.video.playsInline = true;
}
this.targetElement = targEl;
this.element = targEl;
}
}
this.canPlayListener = () => {
this.deactivateStreamPlayingEventExceptionTimeout();
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
};
}
/**
* See {@link EventDispatcher.on}
*/
on<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
super.onAux(type, "Event '" + type + "' triggered by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
if (type === 'videoElementCreated') {
if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
this.lazyLaunchVideoElementCreatedEvent = false;
}
}
if (type === 'streamPlaying') {
if (
this.videos[0] &&
this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4
) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
}
}
if (this.stream.hasAudio) {
if (type === 'publisherStartSpeaking') {
this.stream.enableHarkSpeakingEvent();
}
if (type === 'publisherStopSpeaking') {
this.stream.enableHarkStoppedSpeakingEvent();
}
if (type === 'streamAudioVolumeChange') {
this.stream.enableHarkVolumeChangeEvent(false);
}
}
return this;
}
/**
* See {@link EventDispatcher.once}
*/
once<K extends keyof StreamManagerEventMap>(type: K, handler: (event: StreamManagerEventMap[K]) => void): this {
super.onceAux(type, "Event '" + type + "' triggered once by '" + (this.remote ? 'Subscriber' : 'Publisher') + "'", handler);
if (type === 'videoElementCreated') {
if (!!this.stream && this.lazyLaunchVideoElementCreatedEvent) {
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(this.videos[0].video, this, 'videoElementCreated')]);
}
}
if (type === 'streamPlaying') {
if (
this.videos[0] &&
this.videos[0].video &&
this.videos[0].video.currentTime > 0 &&
this.videos[0].video.paused === false &&
this.videos[0].video.ended === false &&
this.videos[0].video.readyState === 4
) {
this.ee.emitEvent('streamPlaying', [new StreamManagerEvent(this, 'streamPlaying', undefined)]);
}
}
if (this.stream.hasAudio) {
if (type === 'publisherStartSpeaking') {
this.stream.enableOnceHarkSpeakingEvent();
}
if (type === 'publisherStopSpeaking') {
this.stream.enableOnceHarkStoppedSpeakingEvent();
}
if (type === 'streamAudioVolumeChange') {
this.stream.enableOnceHarkVolumeChangeEvent(false);
}
}
return this;
}
/**
* See {@link EventDispatcher.off}
*/
off<K extends keyof StreamManagerEventMap>(type: K, handler?: (event: StreamManagerEventMap[K]) => void): this {
super.offAux(type, handler);
if (type === 'publisherStartSpeaking') {
// Both StreamManager and Session can have "publisherStartSpeaking" event listeners
const remainingStartSpeakingEventListeners =
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStartSpeakingEventListeners === 0) {
this.stream.disableHarkSpeakingEvent(false);
}
}
if (type === 'publisherStopSpeaking') {
// Both StreamManager and Session can have "publisherStopSpeaking" event listeners
const remainingStopSpeakingEventListeners =
this.ee.getListeners(type).length + this.stream.session.ee.getListeners(type).length;
if (remainingStopSpeakingEventListeners === 0) {
this.stream.disableHarkStoppedSpeakingEvent(false);
}
}
if (type === 'streamAudioVolumeChange') {
// Only StreamManager can have "streamAudioVolumeChange" event listeners
const remainingVolumeEventListeners = this.ee.getListeners(type).length;
if (remainingVolumeEventListeners === 0) {
this.stream.disableHarkVolumeChangeEvent(false);
}
}
return this;
}
/**
* Makes `video` element parameter display this {@link stream}. This is useful when you are
* [managing the video elements on your own](/en/stable/cheatsheet/manage-videos/#you-take-care-of-the-video-players)
*
* Calling this method with a video already added to other Publisher/Subscriber will cause the video element to be
* disassociated from that previous Publisher/Subscriber and to be associated to this one.
*
* @returns 1 if the video wasn't associated to any other Publisher/Subscriber and has been successfully added to this one.
* 0 if the video was already added to this Publisher/Subscriber. -1 if the video was previously associated to any other
* Publisher/Subscriber and has been successfully disassociated from that one and properly added to this one.
*/
addVideoElement(video: HTMLVideoElement): number {
this.initializeVideoProperties(video);
if (!this.remote && this.stream.displayMyRemote()) {
if (video.srcObject !== this.stream.getMediaStream()) {
video.srcObject = this.stream.getMediaStream();
}
}
// If the video element is already part of this StreamManager do nothing
for (const v of this.videos) {
if (v.video === video) {
return 0;
}
}
let returnNumber = 1;
for (const streamManager of this.stream.session.streamManagers) {
if (streamManager.disassociateVideo(video)) {
returnNumber = -1;
break;
}
}
this.stream.session.streamManagers.forEach((streamManager) => {
streamManager.disassociateVideo(video);
});
this.pushNewStreamManagerVideo({
video,
id: video.id,
canplayListenerAdded: false
});
logger.info('New video element associated to ', this);
return returnNumber;
}
/**
* Creates a new video element displaying this {@link stream}. This allows you to have multiple video elements displaying the same media stream.
*
* #### Events dispatched
*
* The Publisher/Subscriber object will dispatch a `videoElementCreated` event once the HTML video element has been added to DOM. See {@link VideoElementEvent}
*
* @param targetElement HTML DOM element (or its `id` attribute) in which the video element of the Publisher/Subscriber will be inserted
* @param insertMode How the video element will be inserted accordingly to `targetElemet`
*
* @returns The created HTMLVideoElement
*/
createVideoElement(targetElement?: string | HTMLElement, insertMode?: VideoInsertMode): HTMLVideoElement {
let targEl;
if (typeof targetElement === 'string') {
targEl = document.getElementById(targetElement);
if (!targEl) {
throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement);
}
} else if (targetElement instanceof HTMLElement) {
targEl = targetElement;
} else {
throw new Error("The provided 'targetElement' couldn't be resolved to any HTML element: " + targetElement);
}
const video = this.createVideo();
this.initializeVideoProperties(video);
let insMode = !!insertMode ? insertMode : VideoInsertMode.APPEND;
switch (insMode) {
case VideoInsertMode.AFTER:
targEl.parentNode!!.insertBefore(video, targEl.nextSibling);
break;
case VideoInsertMode.APPEND:
targEl.appendChild(video);
break;
case VideoInsertMode.BEFORE:
targEl.parentNode!!.insertBefore(video, targEl);
break;
case VideoInsertMode.PREPEND:
targEl.insertBefore(video, targEl.childNodes[0]);
break;
case VideoInsertMode.REPLACE:
targEl.parentNode!!.replaceChild(video, targEl);
break;
default:
insMode = VideoInsertMode.APPEND;
targEl.appendChild(video);
break;
}
const v: StreamManagerVideo = {
targetElement: targEl,
video,
insertMode: insMode,
id: video.id,
canplayListenerAdded: false
};
this.pushNewStreamManagerVideo(v);
this.ee.emitEvent('videoElementCreated', [new VideoElementEvent(v.video, this, 'videoElementCreated')]);
this.lazyLaunchVideoElementCreatedEvent = !!this.firstVideoElement;
return video;
}
/**
* Updates the current configuration for the {@link PublisherSpeakingEvent} feature and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/StreamManagerEvent.html) feature for this specific
* StreamManager audio stream, overriding the global options set with {@link OpenVidu.setAdvancedConfiguration}. This way you can customize the audio events options
* for each specific StreamManager and change them dynamically.
*
* @param publisherSpeakingEventsOptions New options to be applied to this StreamManager's audio stream. It is an object which includes the following optional properties:
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_, _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*/
updatePublisherSpeakingEventsOptions(publisherSpeakingEventsOptions: { interval?: number; threshold?: number }): void {
const currentHarkOptions = !!this.stream.harkOptions
? this.stream.harkOptions
: this.stream.session.openvidu.advancedConfiguration.publisherSpeakingEventsOptions || {};
const newInterval =
typeof publisherSpeakingEventsOptions.interval === 'number'
? publisherSpeakingEventsOptions.interval
: typeof currentHarkOptions.interval === 'number'
? currentHarkOptions.interval
: 100;
const newThreshold =
typeof publisherSpeakingEventsOptions.threshold === 'number'
? publisherSpeakingEventsOptions.threshold
: typeof currentHarkOptions.threshold === 'number'
? currentHarkOptions.threshold
: -50;
this.stream.harkOptions = {
interval: newInterval,
threshold: newThreshold
};
if (!!this.stream.speechEvent) {
this.stream.speechEvent.setInterval(newInterval);
this.stream.speechEvent.setThreshold(newThreshold);
}
}
/* Hidden methods */
/**
* @hidden
*/
initializeVideoProperties(video: HTMLVideoElement): void {
if (!(!this.remote && this.stream.displayMyRemote())) {
// Avoid setting the MediaStream into the srcObject if remote subscription before publishing
if (video.srcObject !== this.stream.getMediaStream()) {
// If srcObject already set don't do it again
video.srcObject = this.stream.getMediaStream();
}
}
video.autoplay = true;
video.controls = false;
if (
platform.isSafariBrowser() ||
(platform.isIPhoneOrIPad() &&
(platform.isChromeMobileBrowser() ||
platform.isEdgeMobileBrowser() ||
platform.isOperaMobileBrowser() ||
platform.isFirefoxMobileBrowser()))
) {
video.playsInline = true;
}
if (!video.id) {
video.id = (this.remote ? 'remote-' : 'local-') + 'video-' + this.stream.streamId;
// DEPRECATED property: assign once the property id if the user provided a valid targetElement
if (!this.id && !!this.targetElement) {
this.id = video.id;
}
}
if (this.remote && this.isMirroredVideo(video)) {
// Subscriber video associated to a previously mirrored video element
this.removeMirrorVideo(video);
} else if (!this.remote && !this.stream.displayMyRemote()) {
// Publisher video
video.muted = true;
if (this.isMirroredVideo(video) && !this.stream.outboundStreamOpts.publisherProperties.mirror) {
// If the video was already rotated and now is set to not mirror
this.removeMirrorVideo(video);
} else if (this.stream.outboundStreamOpts.publisherProperties.mirror && !this.stream.isSendScreen()) {
// If the video is now set to mirror and is not screen share
this.mirrorVideo(video);
}
}
}
/**
* @hidden
*/
removeAllVideos(): void {
for (let i = this.stream.session.streamManagers.length - 1; i >= 0; --i) {
if (this.stream.session.streamManagers[i] === this) {
this.stream.session.streamManagers.splice(i, 1);
}
}
this.videos.forEach((streamManagerVideo) => {
// Remove oncanplay event listener (only OpenVidu browser listener, not the user ones)
if (!!streamManagerVideo.video && !!streamManagerVideo.video.removeEventListener) {
streamManagerVideo.video.removeEventListener('canplay', this.canPlayListener);
}
streamManagerVideo.canplayListenerAdded = false;
if (!!streamManagerVideo.targetElement) {
// Only remove from DOM videos created by OpenVidu Browser (those generated by passing a valid targetElement in OpenVidu.initPublisher
// and Session.subscribe or those created by StreamManager.createVideoElement). All this videos triggered a videoElementCreated event
streamManagerVideo.video.parentNode!.removeChild(streamManagerVideo.video);
this.ee.emitEvent('videoElementDestroyed', [
new VideoElementEvent(streamManagerVideo.video, this, 'videoElementDestroyed')
]);
}
// Remove srcObject from the video
this.removeSrcObject(streamManagerVideo);
// Remove from collection of videos every video managed by OpenVidu Browser
this.videos = this.videos.filter((v) => !v.targetElement);
});
}
/**
* @hidden
*/
disassociateVideo(video: HTMLVideoElement): boolean {
let disassociated = false;
for (let i = 0; i < this.videos.length; i++) {
if (this.videos[i].video === video) {
this.videos[i].video.removeEventListener('canplay', this.canPlayListener);
this.videos.splice(i, 1);
disassociated = true;
logger.info('Video element disassociated from ', this);
break;
}
}
return disassociated;
}
/**
* @hidden
*/
addPlayEventToFirstVideo() {
if (!!this.videos[0] && !!this.videos[0].video && !this.videos[0].canplayListenerAdded) {
this.activateStreamPlayingEventExceptionTimeout();
this.videos[0].video.addEventListener('canplay', this.canPlayListener);
this.videos[0].canplayListenerAdded = true;
}
}
/**
* @hidden
*/
updateMediaStream(mediaStream: MediaStream) {
this.videos.forEach((streamManagerVideo) => {
streamManagerVideo.video.srcObject = mediaStream;
if (platform.isIonicIos()) {
// iOS Ionic. LIMITATION: must reinsert the video in the DOM for
// the media stream to be updated
const vParent = streamManagerVideo.video.parentElement;
const newVideo = streamManagerVideo.video;
vParent!!.replaceChild(newVideo, streamManagerVideo.video);
streamManagerVideo.video = newVideo;
}
});
}
/**
* @hidden
*/
emitEvent(type: string, eventArray: any[]): void {
this.ee.emitEvent(type, eventArray);
}
/**
* @hidden
*/
createVideo(): HTMLVideoElement {
return document.createElement('video');
}
/**
* @hidden
*/
removeSrcObject(streamManagerVideo: StreamManagerVideo) {
streamManagerVideo.video.srcObject = null;
this.deactivateStreamPlayingEventExceptionTimeout();
}
/**
* @hidden
*/
abstract replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void;
/* Private methods */
protected pushNewStreamManagerVideo(streamManagerVideo: StreamManagerVideo) {
this.videos.push(streamManagerVideo);
this.addPlayEventToFirstVideo();
if (this.stream.session.streamManagers.indexOf(this) === -1) {
this.stream.session.streamManagers.push(this);
}
}
private mirrorVideo(video: HTMLVideoElement): void {
if (!platform.isIonicIos()) {
video.style.transform = 'rotateY(180deg)';
video.style.webkitTransform = 'rotateY(180deg)';
}
}
private removeMirrorVideo(video: HTMLVideoElement): void {
video.style.transform = 'unset';
video.style.webkitTransform = 'unset';
}
private isMirroredVideo(video: HTMLVideoElement): boolean {
return video.style.transform === 'rotateY(180deg)' || video.style.webkitTransform === 'rotateY(180deg)';
}
private activateStreamPlayingEventExceptionTimeout() {
if (!this.remote) {
// ExceptionEvent NO_STREAM_PLAYING_EVENT is only for subscribers
return;
}
if (this.streamPlayingEventExceptionTimeout != null) {
// The timeout is already activated
return;
}
// Trigger ExceptionEvent NO_STREAM_PLAYING_EVENT if after timeout there is no 'canplay' event
const msTimeout = this.stream.session.openvidu.advancedConfiguration.noStreamPlayingEventExceptionTimeout || 4000;
this.streamPlayingEventExceptionTimeout = setTimeout(() => {
const msg =
'StreamManager of Stream ' +
this.stream.streamId +
' (' +
(this.remote ? 'Subscriber' : 'Publisher') +
') did not trigger "streamPlaying" event in ' +
msTimeout +
' ms';
logger.warn(msg);
this.stream.session.emitEvent('exception', [
new ExceptionEvent(this.stream.session, ExceptionEventName.NO_STREAM_PLAYING_EVENT, (<any>this) as Subscriber, msg)
]);
delete this.streamPlayingEventExceptionTimeout;
}, msTimeout);
}
private deactivateStreamPlayingEventExceptionTimeout() {
clearTimeout(this.streamPlayingEventExceptionTimeout as any);
delete this.streamPlayingEventExceptionTimeout;
}
}

View File

@ -1,101 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Stream } from './Stream';
import { StreamManager } from './StreamManager';
import { SubscriberProperties } from '../OpenViduInternal/Interfaces/Public/SubscriberProperties';
import { OpenViduLogger } from '../OpenViduInternal/Logger/OpenViduLogger';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* Packs remote media streams. Participants automatically receive them when others publish their streams. Initialized with {@link Session.subscribe} method
*
* See available event listeners at {@link StreamManagerEventMap}.
*/
export class Subscriber extends StreamManager {
/**
* @hidden
*/
properties: SubscriberProperties;
/**
* @hidden
*/
constructor(stream: Stream, targEl: string | HTMLElement | undefined, properties: SubscriberProperties) {
super(stream, targEl);
this.element = this.targetElement;
this.stream = stream;
this.properties = properties;
}
/**
* Subscribe or unsubscribe from the audio stream (if available). Calling this method twice in a row passing same value will have no effect
* @param value `true` to subscribe to the audio stream, `false` to unsubscribe from it
*/
subscribeToAudio(value: boolean): Subscriber {
this.stream
.getMediaStream()
.getAudioTracks()
.forEach((track) => {
track.enabled = value;
});
this.stream.audioActive = value;
logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its audio stream');
return this;
}
/**
* Subscribe or unsubscribe from the video stream (if available). Calling this method twice in a row passing same value will have no effect
* @param value `true` to subscribe to the video stream, `false` to unsubscribe from it
*/
subscribeToVideo(value: boolean): Subscriber {
this.stream
.getMediaStream()
.getVideoTracks()
.forEach((track) => {
track.enabled = value;
});
this.stream.videoActive = value;
logger.info("'Subscriber' has " + (value ? 'subscribed to' : 'unsubscribed from') + ' its video stream');
return this;
}
/* Hidden methods */
/**
* @hidden
*/
replaceTrackInMediaStream(track: MediaStreamTrack, updateLastConstraints: boolean): void {
const mediaStream: MediaStream = this.stream.getMediaStream();
let removedTrack: MediaStreamTrack;
if (track.kind === 'video') {
removedTrack = mediaStream.getVideoTracks()[0];
if (updateLastConstraints) {
this.stream.lastVideoTrackConstraints = track.getConstraints();
}
} else {
removedTrack = mediaStream.getAudioTracks()[0];
}
mediaStream.removeTrack(removedTrack);
removedTrack.stop();
mediaStream.addTrack(track);
}
}

View File

@ -1,35 +0,0 @@
{
//"allowUnusedLabels": true,
"allowUnreachableCode": false,
"buildOnSave": false,
"compileOnSave": true,
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"emitBOM": false,
"forceConsistentCasingInFileNames": true,
"lib": [
"dom",
"es2015.promise",
"es5",
"scripthost"
],
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
//"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"outDir": "../../lib",
"preserveConstEnums": true,
"removeComments": true,
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5"
}
}

View File

@ -1,23 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export enum LocalRecorderState {
READY = 'READY',
RECORDING = 'RECORDING',
PAUSED = 'PAUSED',
FINISHED = 'FINISHED'
}

View File

@ -1,141 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* Defines property {@link OpenViduError.name}
*/
export enum OpenViduErrorName {
/**
* Browser is not supported by OpenVidu.
* Returned upon unsuccessful {@link Session.connect}
*/
BROWSER_NOT_SUPPORTED = 'BROWSER_NOT_SUPPORTED',
/**
* The user hasn't granted permissions to the required input device when the browser asked for them.
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
DEVICE_ACCESS_DENIED = 'DEVICE_ACCESS_DENIED',
/**
* The required input device is probably being used by other process when the browser asked for it.
* This error can also be triggered when the user granted permission to use the devices but a hardware
* error occurred at the OS, browser or web page level, which prevented access to the device.
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
DEVICE_ALREADY_IN_USE = 'DEVICE_ALREADY_IN_USE',
/**
* The user hasn't granted permissions to capture some desktop screen when the browser asked for them.
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
SCREEN_CAPTURE_DENIED = 'SCREEN_CAPTURE_DENIED',
/**
* Browser does not support screen sharing.
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
SCREEN_SHARING_NOT_SUPPORTED = 'SCREEN_SHARING_NOT_SUPPORTED',
/**
* Only for Chrome, there's no screen sharing extension installed
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
SCREEN_EXTENSION_NOT_INSTALLED = 'SCREEN_EXTENSION_NOT_INSTALLED',
/**
* Only for Chrome, the screen sharing extension is installed but is disabled
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
SCREEN_EXTENSION_DISABLED = 'SCREEN_EXTENSION_DISABLED',
/**
* No video input device found with the provided deviceId (property {@link PublisherProperties.videoSource})
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
INPUT_VIDEO_DEVICE_NOT_FOUND = 'INPUT_VIDEO_DEVICE_NOT_FOUND',
/**
* No audio input device found with the provided deviceId (property {@link PublisherProperties.audioSource})
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
INPUT_AUDIO_DEVICE_NOT_FOUND = 'INPUT_AUDIO_DEVICE_NOT_FOUND',
/**
* There was an unknown error when trying to access the specified audio device
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
INPUT_AUDIO_DEVICE_GENERIC_ERROR = 'INPUT_AUDIO_DEVICE_GENERIC_ERROR',
/**
* Method {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia} has been called with properties `videoSource` and `audioSource` of
* {@link PublisherProperties} parameter both set to *false* or *null*
*/
NO_INPUT_SOURCE_SET = 'NO_INPUT_SOURCE_SET',
/**
* Some media property of {@link PublisherProperties} such as `frameRate` or `resolution` is not supported
* by the input devices (whenever it is possible they are automatically adjusted to the most similar value).
* Returned upon unsuccessful {@link OpenVidu.initPublisher} or {@link OpenVidu.getUserMedia}
*/
PUBLISHER_PROPERTIES_ERROR = 'PUBLISHER_PROPERTIES_ERROR',
/**
* The client tried to call a method without the required permissions. This can occur for methods {@link Session.publish},
* {@link Session.forceUnpublish}, {@link Session.forceDisconnect}, {@link Stream.applyFilter}, {@link Stream.removeFilter}
*/
OPENVIDU_PERMISSION_DENIED = 'OPENVIDU_PERMISSION_DENIED',
/**
* There is no connection to the Session. This error will be thrown when any method requiring a connection to
* openvidu-server is called before successfully calling method {@link Session.connect}
*/
OPENVIDU_NOT_CONNECTED = 'OPENVIDU_NOT_CONNECTED',
/**
* Error related to [Virtual Background](/en/stable/advanced-features/virtual-background/)
*/
VIRTUAL_BACKGROUND_ERROR = 'VIRTUAL_BACKGROUND_ERROR',
/**
* Generic error
*/
GENERIC_ERROR = 'GENERIC_ERROR'
}
/**
* Simple object to identify runtime errors on the client side
*/
export class OpenViduError {
/**
* Uniquely identifying name of the error
*/
name: OpenViduErrorName;
/**
* Full description of the error
*/
message: string;
/**
* @hidden
*/
constructor(name: OpenViduErrorName, message: string) {
this.name = name;
this.message = message;
}
}

View File

@ -1,23 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export enum TypeOfVideo {
CAMERA = 'CAMERA',
SCREEN = 'SCREEN',
CUSTOM = 'CUSTOM',
IPCAM = 'IPCAM'
}

View File

@ -1,42 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* How the video will be inserted in the DOM for Publishers and Subscribers. See {@link PublisherProperties.insertMode} and {@link SubscriberProperties.insertMode}
*/
export enum VideoInsertMode {
/**
* Video inserted after the target element (as next sibling)
*/
AFTER = 'AFTER',
/**
* Video inserted as last child of the target element
*/
APPEND = 'APPEND',
/**
* Video inserted before the target element (as previous sibling)
*/
BEFORE = 'BEFORE',
/**
* Video inserted as first child of the target element
*/
PREPEND = 'PREPEND',
/**
* Video replaces target element
*/
REPLACE = 'REPLACE'
}

View File

@ -1,61 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
import { ConnectionEventReason } from './Types/Types';
/**
* Triggered by:
* - {@link SessionEventMap.connectionCreated}
* - {@link SessionEventMap.connectionDestroyed}
*/
export class ConnectionEvent extends Event {
/**
* Connection object that was created or destroyed
*/
connection: Connection;
/**
* For `connectionDestroyed` event:
* - "disconnect": the remote user has called `Session.disconnect()`
* - "forceDisconnectByUser": the remote user has been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": the remote user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the remote user network connection has dropped
* - "nodeCrashed": a node has crashed in the server side
*
* For `connectionCreated` event an empty string
*/
reason: ConnectionEventReason;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session, type: string, connection: Connection, reason: ConnectionEventReason) {
super(cancelable, target, type);
this.connection = connection;
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -1,68 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
import { Event } from './Event';
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Triggered by {@link SessionEventMap.connectionPropertyChanged}
*/
export class ConnectionPropertyChangedEvent extends Event {
/**
* The Connection whose property has changed
*/
connection: Connection;
/**
* The property of the stream that changed. This value is either `"role"` or `"record"`
*/
changedProperty: string;
/**
* New value of the property (after change, current value)
*/
newValue: Object;
/**
* Previous value of the property (before change)
*/
oldValue: Object;
/**
* @hidden
*/
constructor(target: Session, connection: Connection, changedProperty: string, newValue: Object, oldValue: Object) {
super(false, target, 'connectionPropertyChanged');
this.connection = connection;
this.changedProperty = changedProperty;
this.newValue = newValue;
this.oldValue = oldValue;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,83 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Filter } from '../../OpenVidu/Filter';
import { StreamManager } from '../../OpenVidu/StreamManager';
import { Session } from '../../OpenVidu/Session';
export abstract class Event {
/**
* Whether the event has a default behavior that may be prevented by calling {@link Event.preventDefault}
*/
cancelable: boolean;
/**
* The object that dispatched the event
*/
target: Session | StreamManager | Filter;
/**
* The type of event. This is the same string you pass as first parameter when calling method `on()` of any object implementing {@link EventDispatcher} interface
*/
type: string;
/**
* @hidden
*/
hasBeenPrevented = false;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session | StreamManager | Filter, type: string) {
this.cancelable = cancelable;
this.target = target;
this.type = type;
}
/**
* Whether the default beahivour of the event has been prevented or not. Call {@link Event.preventDefault} to prevent it
*/
isDefaultPrevented(): boolean {
return this.hasBeenPrevented;
}
/**
* Prevents the default behavior of the event. The following events have a default behavior:
*
* - `sessionDisconnected`: dispatched by {@link Session} object, automatically unsubscribes the leaving participant from every Subscriber object of the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to each Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method {@link Session.subscribe} or
* by calling {@link Subscriber.createVideoElement}). For every video removed, each Subscriber object will also dispatch a `videoElementDestroyed` event.
*
* - `streamDestroyed`:
* - If dispatched by a {@link Publisher} (*you* have unpublished): automatically stops all media tracks and deletes any HTML video element associated to it (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement`
* in method {@link OpenVidu.initPublisher} or by calling {@link Publisher.createVideoElement}). For every video removed, the Publisher object will also dispatch a `videoElementDestroyed` event.
* - If dispatched by {@link Session} (*other user* has unpublished): automatically unsubscribes the proper Subscriber object from the session (this includes closing the RTCPeerConnection and disposing all MediaStreamTracks)
* and also deletes any HTML video element associated to that Subscriber (only those created by OpenVidu Browser, either by passing a valid parameter as `targetElement` in method {@link Session.subscribe} or
* by calling {@link Subscriber.createVideoElement}). For every video removed, the Subscriber object will also dispatch a `videoElementDestroyed` event.
*/
preventDefault() {
// tslint:disable-next-line:no-empty
this.callDefaultBehavior = () => {};
this.hasBeenPrevented = true;
}
/**
* @hidden
*/
abstract callDefaultBehavior();
}

View File

@ -1,21 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* All OpenVidu Browser events inherit from this interface
*/
export interface EventMap {}

View File

@ -1,80 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { StreamEvent } from '../StreamEvent';
import { StreamManagerEventMap } from './StreamManagerEventMap';
/**
* Events dispatched by {@link Publisher} object. Manage event listeners with
* {@link Publisher.on}, {@link Publisher.once} and {@link Publisher.off} methods.
*
* Example:
*
* ```javascript
* publisher.on('accessDenied', () => {
* console.error('Camera access has been denied!');
* }
*
* publisher.off('accessDenied');
* ```
*/
export interface PublisherEventMap extends StreamManagerEventMap {
/**
* Event dispatched when the {@link Publisher} has been published to the session (see {@link Session.publish}).
*/
streamCreated: StreamEvent;
/**
* Event dispatched when the {@link Publisher} has been unpublished from the session.
*/
streamDestroyed: StreamEvent;
/**
* Event dispatched when a Publisher tries to access some media input device and has the required permissions to do so.
*
* This happens when calling {@link OpenVidu.initPublisher} or {@link OpenVidu.initPublisherAsync} and the application
* has permissions to use the devices. This usually means the user has accepted the permissions dialog that the
* browser will show when trying to access the camera/microphone/screen.
*/
accessAllowed: never;
/**
* Event dispatched when a Publisher tries to access some media input device and does NOT have the required permissions to do so.
*
* This happens when calling {@link OpenVidu.initPublisher} or {@link OpenVidu.initPublisherAsync} and the application
* lacks the required permissions to use the devices. This usually means the user has NOT accepted the permissions dialog that the
* browser will show when trying to access the camera/microphone/screen.
*/
accessDenied: never;
/**
* Event dispatched when the pop-up shown by the browser to request permissions for the input media devices is opened.
*
* You can use this event to alert the user about granting permissions for your website. Note that this event is artificially
* generated based only on time intervals when accessing media devices. A heavily overloaded client device that simply takes more
* than usual to access the media device could produce a false trigger of this event.
*/
accessDialogOpened: never;
/**
* Event dispatched after the user clicks on "Allow" or "Block" in the pop-up shown by the browser to request permissions
* for the input media devices.
*
* This event can only be triggered after an {@link accessDialogOpened} event has been previously triggered.
*/
accessDialogClosed: never;
}

View File

@ -1,225 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { EventMap } from './EventMap';
import { ConnectionEvent } from '../ConnectionEvent';
import { ConnectionPropertyChangedEvent } from '../ConnectionPropertyChangedEvent';
import { ExceptionEvent } from '../ExceptionEvent';
import { NetworkQualityLevelChangedEvent } from '../NetworkQualityLevelChangedEvent';
import { PublisherSpeakingEvent } from '../PublisherSpeakingEvent';
import { RecordingEvent } from '../RecordingEvent';
import { SessionDisconnectedEvent } from '../SessionDisconnectedEvent';
import { SignalEvent } from '../SignalEvent';
import { SpeechToTextEvent } from '../SpeechToTextEvent';
import { StreamEvent } from '../StreamEvent';
import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
/**
* Events dispatched by {@link Session} object. Manage event listeners with
* {@link Session.on}, {@link Session.once} and {@link Session.off} methods.
*
* Example:
*
* ```javascript
* session.on('connectionCreated', (event) => {
* console.log('Connection ' + event.connection.connectionId + ' created');
* }
*
* session.off('connectionDestroyed');
* ```
*/
export interface SessionEventMap extends EventMap {
/**
* Event dispatched when a new user has connected to the session.
*
* It is fired for both the local user and remote users.
*/
connectionCreated: ConnectionEvent;
/**
* Event dispatched when a remote user has left the session.
*
* For the local user see {@link sessionDisconnected}.
*/
connectionDestroyed: ConnectionEvent;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when a property of the local {@link Connection} object changes.
*
* It is fired only for the local user.
*
* The properties that may change are {@link Connection.role} and {@link Connection.record}.
* The only way the Connection properties may change is by updating them through:
*
* - [API REST](/en/stable/reference-docs/REST-API/#patch-connection)
* - [openvidu-java-client](/en/stable/reference-docs/openvidu-java-client/#update-a-connection)
* - [openvidu-node-client](/en/stable/reference-docs/openvidu-node-client/#update-a-connection)<br><br>
*/
connectionPropertyChanged: ConnectionPropertyChangedEvent;
/**
* Event dispatched when the local user has left the session.
*
* For remote users see {@link connectionDestroyed}.
*/
sessionDisconnected: SessionDisconnectedEvent;
/**
* Event dispatched when a user has started publishing media to the session (see {@link Session.publish}).
*
* It is fired for both the local user and remote users.
*/
streamCreated: StreamEvent;
/**
* Event dispatched when a user stops publishing media to the session.
*
* It is fired for both the local user and remote users.
*/
streamDestroyed: StreamEvent;
/**
* Event dispatched when a Stream undergoes any change in any of its mutable properties
* (see {@link StreamPropertyChangedEvent.changedProperty}).
*
* It is fired for both remote streams (owned by a {@link Subscriber}) or local streams (owned by a {@link Publisher}).
*/
streamPropertyChanged: StreamPropertyChangedEvent;
/**
* Event dispatched when a user has started speaking.
*
* It is fired for both the local user and remote users.
*
* Extra information:
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
*/
publisherStartSpeaking: PublisherSpeakingEvent;
/**
* Event dispatched when a user has stopped speaking.
*
* It is fired for both the local user and remote users.
*
* Extra information:
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
*/
publisherStopSpeaking: PublisherSpeakingEvent;
/**
* @hidden
*/
[key: `signal:${string}`]: SignalEvent;
/**
* Event dispatched when a signal is received (see [Send text messages between users](/en/stable/cheatsheet/send-messages)).
*
* If the listener is added as **`signal:TYPE`**, only signals of type **`TYPE`** will trigger the event.
*/
signal: SignalEvent;
/**
* Event dispatched when the session has started being recorded.
*
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
*/
recordingStarted: RecordingEvent;
/**
* Event dispatched when the session has stopped being recorded.
*
* Property **`OPENVIDU_RECORDING_NOTIFICATION`** of [the OpenVidu deployment configuration](/en/stable/reference-docs/openvidu-config/)
* defines which users should receive this events (by default, only users with role `PUBLISHER` or `MODERATOR`)
*/
recordingStopped: RecordingEvent;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when the session has started being broadcasted. See [Broadcast to YouTube/Twitch](/en/stable/advanced-features/broadcast/)
*/
broadcastStarted: never;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when the session has stopped being broadcasted. See [Broadcast to YouTube/Twitch](/en/stable/advanced-features/broadcast/)
*/
broadcastStopped: never;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when the network quality level of a {@link Connection} changes. See [network quality](/en/stable/advanced-features/network-quality/).
*/
networkQualityLevelChanged: NetworkQualityLevelChangedEvent;
/**
* **This feature is part of OpenVidu
* <a href="https://docs.openvidu.io/en/stable/openvidu-pro/" style="display: inline-block; background-color: rgb(0, 136, 170); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">PRO</a>
* and
* <a href="https://docs.openvidu.io/en/stable/openvidu-enterprise/" style="display: inline-block; background-color: rgb(156, 39, 176); color: white; font-weight: bold; padding: 0px 5px; margin: 0 2px 0 2px; border-radius: 3px; font-size: 13px; line-height:21px; text-decoration: none; font-family: Montserrat, sans-serif">ENTERPRISE</a>
* editions**
*
* Event dispatched when a speech-to-text message has been received for certain Stream. See [Speech To Text](/en/stable/advanced-features/speech-to-text/).
*/
speechToTextMessage: SpeechToTextEvent;
/**
* Event dispatched when the local user has lost its connection to the session, and starts the automatic reconnection process.
*
* See [Reconnection events](/en/stable/advanced-features/automatic-reconnection/#reconnection-events).
*/
reconnecting: never;
/**
* Event dispatched when the local user has successfully recovered its connection to the session after losing it.
*
* If the connection was recovered but OpenVidu Server already evicted the user due to timeout, then this event will
* not be dispatched. A {@link sessionDisconnected} event with reason `networkDisconnect` will be triggered instead.
*
* See [Reconnection events](/en/stable/advanced-features/automatic-reconnection/#reconnection-events).
*/
reconnected: never;
/**
* This event acts as a global handler for asynchronous errors that may be triggered for multiple reasons and from multiple origins.
* To see the different types of exceptions go to {@link ExceptionEventName}.
*/
exception: ExceptionEvent;
}

View File

@ -1,90 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { EventMap } from './EventMap';
import { PublisherSpeakingEvent } from '../PublisherSpeakingEvent';
import { StreamManagerEvent } from '../StreamManagerEvent';
import { StreamPropertyChangedEvent } from '../StreamPropertyChangedEvent';
import { VideoElementEvent } from '../VideoElementEvent';
/**
* Events dispatched by {@link StreamManager} object. Manage event listeners with
* {@link StreamManager.on}, {@link StreamManager.once} and {@link StreamManager.off} methods.
*
* Example:
*
* ```javascript
* streamManager.on('videoElementCreated', (event) => {
* console.log('New video element created:', event.element);
* }
*
* streamManager.off('videoElementCreated');
* ```
*/
export interface StreamManagerEventMap extends EventMap {
/**
* Event dispatched when a new HTML video element has been inserted into DOM by OpenVidu Browser library. See
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
*/
videoElementCreated: VideoElementEvent;
/**
* Event dispatched when an HTML video element has been removed from DOM by OpenVidu Browser library. See
* [Manage video players](/en/stable/cheatsheet/manage-videos) section.
*/
videoElementDestroyed: VideoElementEvent;
/**
* Event dispatched when the media stream starts playing (one of its videos has media and has begun to play).
* This event will be dispatched when these 3 conditions are met:
* 1. The StreamManager has no video associated in the DOM.
* 2. It is associated to one video.
* 3. That video starts playing. Internally the expected Web API event is [HTMLMediaElement.canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event).
*/
streamPlaying: StreamManagerEvent;
/**
* Event dispatched when the volume of the media stream's audio track changes. Only applies if {@link Stream.hasAudio} is `true`.
* The frequency this event is fired with is defined by property `interval` of
* {@link OpenViduAdvancedConfiguration.publisherSpeakingEventsOptions} (default 100ms)
*/
streamAudioVolumeChange: StreamManagerEvent;
/**
* Event dispatched when a Stream undergoes any change in any of its mutable properties
* (see {@link StreamPropertyChangedEvent.changedProperty}).
*/
streamPropertyChanged: StreamPropertyChangedEvent;
/**
* Event dispatched when the user owning the stream has started speaking.
*
* Extra information:
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
*/
publisherStartSpeaking: PublisherSpeakingEvent;
/**
* Event dispatched when the user owning the stream has stopped speaking.
*
* Extra information:
* - This event will only be triggered for **streams that have audio tracks** ({@link Stream.hasAudio} must be true).
* - Further configuration can be applied on how the event is dispatched by setting property `publisherSpeakingEventsOptions` in the call of {@link OpenVidu.setAdvancedConfiguration}.
*/
publisherStopSpeaking: PublisherSpeakingEvent;
}

View File

@ -1,136 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { Subscriber } from '../../OpenVidu/Subscriber';
import { Event } from './Event';
/**
* Defines property {@link ExceptionEvent.name}
*/
export enum ExceptionEventName {
/**
* There was an unexpected error on the server-side processing an ICE candidate generated and sent by the client-side.
*
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Session} object.
*/
ICE_CANDIDATE_ERROR = 'ICE_CANDIDATE_ERROR',
/**
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `failed` status.
*
* This is a terminal error that won't have any kind of possible recovery. If the client is still connected to OpenVidu Server,
* then an automatic reconnection process of the media stream is immediately performed. If the ICE connection has broken due to
* a total network drop, then no automatic reconnection process will be possible.
*
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Stream} object.
*/
ICE_CONNECTION_FAILED = 'ICE_CONNECTION_FAILED',
/**
* The [ICE connection state](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/iceConnectionState)
* of an [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) reached `disconnected` status.
*
* This is not a terminal error, and it is possible for the ICE connection to be reconnected. If the client is still connected to
* OpenVidu Server and after certain timeout the ICE connection has not reached a success or terminal status, then an automatic
* reconnection process of the media stream is performed. If the ICE connection has broken due to a total network drop, then no
* automatic reconnection process will be possible.
*
* You can customize the timeout for the reconnection attempt with property {@link OpenViduAdvancedConfiguration.iceConnectionDisconnectedExceptionTimeout},
* which by default is 4000 milliseconds.
*
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Stream} object.
*/
ICE_CONNECTION_DISCONNECTED = 'ICE_CONNECTION_DISCONNECTED',
/**
* A {@link Subscriber} object has not fired event `streamPlaying` after certain timeout. `streamPlaying` event belongs to {@link StreamManagerEvent}
* category. It wraps Web API native event [canplay](https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/canplay_event).
*
* OpenVidu Browser can take care of the video players (see [here](/en/stable/cheatsheet/manage-videos/#let-openvidu-take-care-of-the-video-players)),
* or you can take care of video players on your own (see [here](/en/stable/cheatsheet/manage-videos/#you-take-care-of-the-video-players)).
* Either way, whenever a {@link Subscriber} object is commanded to attach its {@link Stream} to a video element, it is supposed to fire `streamPlaying`
* event shortly after. If it does not, then we can safely assume that something wrong has happened while playing the remote video and the
* application may be notified through this specific ExceptionEvent.
*
* The timeout can be configured with property {@link OpenViduAdvancedConfiguration.noStreamPlayingEventExceptionTimeout}. By default it is 4000 milliseconds.
*
* This is just an informative exception. It only means that a remote Stream that is supposed to be playing by a video player has not done so
* in a reasonable time. But the lack of the event can be caused by multiple reasons. If a Subscriber is not playing its Stream, the origin
* of the problem could be located at the Publisher side. Or may be caused by a transient network problem. But it also could be a problem with
* autoplay permissions. Bottom line, the cause can be very varied, and depending on the application the lack of the event could even be expected.
*
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Subscriber} object.
*/
NO_STREAM_PLAYING_EVENT = 'NO_STREAM_PLAYING_EVENT',
/**
* There has been a server-side disconnection of the Speech To Text module. From the moment this exception is fired to the moment method
* {@link Session.subscribeToSpeechToText} is called again, the transcription of the audio stream will not be available and no {@link SpeechToTextEvent}
* will be fired.
*
* {@link ExceptionEvent} objects with this {@link ExceptionEvent.name} will have as {@link ExceptionEvent.origin} property a {@link Session} object.
*/
SPEECH_TO_TEXT_DISCONNECTED = 'SPEECH_TO_TEXT_DISCONNECTED',
}
/**
* Triggered by {@link SessionEventMap.exception}
*/
export class ExceptionEvent extends Event {
/**
* Name of the exception
*/
name: ExceptionEventName;
/**
* Object affected by the exception. Depending on the {@link ExceptionEvent.name} property:
* - {@link Session}: `ICE_CANDIDATE_ERROR`
* - {@link Stream}: `ICE_CONNECTION_FAILED`, `ICE_CONNECTION_DISCONNECTED`
* - {@link Subscriber}: `NO_STREAM_PLAYING_EVENT`
*/
origin: Session | Stream | Subscriber;
/**
* Informative description of the exception
*/
message: string;
/**
* Any extra information associated to the exception
*/
data?: any;
/**
* @hidden
*/
constructor(session: Session, name: ExceptionEventName, origin: Session | Stream | Subscriber, message: string, data?: any) {
super(false, session, 'exception');
this.name = name;
this.origin = origin;
this.message = message;
this.data = data;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -1,43 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Filter } from '../../OpenVidu/Filter';
/**
* Defines every event dispatched by audio/video stream filters. You can subscribe to filter events by calling {@link Filter.addEventListener}
*/
export class FilterEvent extends Event {
/**
* Data of the event
*/
data: Object;
/**
* @hidden
*/
constructor(target: Filter, eventType: string, data: Object) {
super(false, target, eventType);
this.data = data;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,56 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
import { Connection } from '../../OpenVidu/Connection';
/**
* Triggered by {@link SessionEventMap.networkQualityLevelChanged}
*/
export class NetworkQualityLevelChangedEvent extends Event {
/**
* New value of the network quality level
*/
newValue: number;
/**
* Old value of the network quality level
*/
oldValue: number;
/**
* Connection for whom the network quality level changed
*/
connection: Connection;
/**
* @hidden
*/
constructor(target: Session, newValue: number, oldValue: number, connection: Connection) {
super(false, target, 'networkQualityLevelChanged');
this.newValue = newValue;
this.oldValue = oldValue;
this.connection = connection;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,54 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Triggered by:
* - `publisherStartSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStartSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStartSpeaking) objects)
* - `publisherStopSpeaking` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#publisherStopSpeaking) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#publisherStopSpeaking) objects)
*/
export class PublisherSpeakingEvent extends Event {
/**
* The client that started or stopped speaking
*/
connection: Connection;
/**
* The streamId of the Stream affected by the speaking event
*/
streamId: string;
/**
* @hidden
*/
constructor(target: Session | StreamManager, type: string, connection: Connection, streamId: string) {
super(false, target, type);
this.type = type;
this.connection = connection;
this.streamId = streamId;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,71 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
import { RecordingEventReason } from './Types/Types';
/**
* Triggered by:
* - {@link SessionEventMap.recordingStarted}
* - {@link SessionEventMap.recordingStopped}
*/
export class RecordingEvent extends Event {
/**
* The recording ID generated in openvidu-server
*/
id: string;
/**
* The recording name you supplied to openvidu-server. For example, to name your recording file MY_RECORDING:
* - With **API REST**: POST to `/api/recordings/start` passing JSON body `{"session":"sessionId","name":"MY_RECORDING"}`
* - With **openvidu-java-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, new RecordingProperties.Builder().name("MY_RECORDING").build())`
* - With **openvidu-node-client**: `OpenVidu.startRecording(sessionId, "MY_RECORDING")` or `OpenVidu.startRecording(sessionId, {name: "MY_RECORDING"})`
*
* If no name is supplied, this property will be undefined and the recorded file will be named after property {@link id}
*/
name?: string;
/**
* For 'recordingStopped' event:
* - "recordingStoppedByServer": the recording has been gracefully stopped by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "automaticStop": see [Automatic stop of recordings](/en/stable/advanced-features/recording/#automatic-stop-of-recordings)
* - "nodeCrashed": a node has crashed in the server side
*
* For 'recordingStarted' empty string
*/
reason?: RecordingEventReason;
/**
* @hidden
*/
constructor(target: Session, type: string, id: string, name: string, reason?: RecordingEventReason) {
super(false, target, type);
this.id = id;
if (name !== id) {
this.name = name;
}
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -1,80 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
import { OpenViduLogger } from '../Logger/OpenViduLogger';
import { ConnectionEventReason } from './Types/Types';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* Triggered by {@link SessionEventMap.sessionDisconnected}
*/
export class SessionDisconnectedEvent extends Event {
/**
* - "disconnect": you have called `Session.disconnect()`
* - "forceDisconnectByUser": you have been evicted from the Session by other user calling `Session.forceDisconnect()`
* - "forceDisconnectByServer": you have been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": your network connection has dropped. Before a SessionDisconnectedEvent with this reason is triggered,
* Session object will always have previously dispatched a `reconnecting` event. If the reconnection process succeeds,
* Session object will dispatch a `reconnected` event. If it fails, Session object will dispatch a SessionDisconnectedEvent
* with reason "networkDisconnect"
* - "nodeCrashed": a node has crashed in the server side. You can use this reason to ask your application's backend to reconnect
* to a new session to replace the crashed one
*/
reason: ConnectionEventReason;
/**
* @hidden
*/
constructor(target: Session, reason: ConnectionEventReason) {
super(true, target, 'sessionDisconnected');
this.reason = reason;
}
/**
* @hidden
*/
callDefaultBehavior() {
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
const session = <Session>this.target;
// Dispose and delete all remote Connections
session.remoteConnections.forEach((remoteConnection) => {
const connectionId = remoteConnection.connectionId;
if (!!session.remoteConnections.get(connectionId)?.stream) {
session.remoteConnections.get(connectionId)?.stream!.disposeWebRtcPeer();
session.remoteConnections.get(connectionId)?.stream!.disposeMediaStream();
if (session.remoteConnections.get(connectionId)?.stream!.streamManager) {
session.remoteConnections.get(connectionId)?.stream!.streamManager.removeAllVideos();
}
const streamId = session.remoteConnections.get(connectionId)?.stream?.streamId;
if (!!streamId) {
session.remoteStreamsCreated.delete(streamId);
}
session.remoteConnections.get(connectionId)?.dispose();
}
session.remoteConnections.delete(connectionId);
});
}
}

View File

@ -1,64 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
/**
* Triggered by {@link SessionEventMap.signal}
*/
export class SignalEvent extends Event {
/**
* The type of signal. It is string `"signal"` for those signals sent with no {@link SignalOptions.type} property, and `"signal:type"` if was sent with a
* valid {@link SignalOptions.type} property.
*
* The client must be specifically subscribed to `Session.on('signal:type', function(signalEvent) {...})` to trigger that type of signal.
*
* Subscribing to `Session.on('signal', function(signalEvent) {...})` will trigger all signals, no matter their type.
*/
type: string;
/**
* The message of the signal (can be empty)
*/
data?: string;
/**
* The client that sent the signal. This property is undefined if the signal
* was directly generated by the application server (not by other client)
*/
from?: Connection;
/**
* @hidden
*/
constructor(target: Session, type?: string, data?: string, from?: Connection) {
super(false, target, 'signal');
if (!!type) {
this.type = 'signal:' + type;
}
this.data = data;
this.from = from;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,72 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Connection } from '../../OpenVidu/Connection';
import { Session } from '../../OpenVidu/Session';
import { SpeechToTextEventReason } from './Types/Types';
/**
* Triggered by {@link SessionEventMap.speechToTextMessage}
*/
export class SpeechToTextEvent extends Event {
/**
* The {@link Connection} owning the Stream that produced the speech-to-text event.
* In other words, this is the participant that spoke and produced this transcription event.
*/
connection: Connection;
/**
* The text of the event. This is the transcription for this specific piece of audio stream
*/
text: string;
/**
* All speech-to-text events are generated
*/
reason: SpeechToTextEventReason;
/**
* The original event from the speech to text engine. This can vary depending on the engine
*/
raw: string;
/**
* [BCP-47](https://tools.ietf.org/html/bcp47) language tag (like "en-US" or "es-ES") of the recognized text. This will be the same as the language provided
* in method {@link Session.subscribeToSpeechToText} method
*/
lang: string;
/**
* @hidden
*/
constructor(target: Session, connection: Connection, text: string, reason: SpeechToTextEventReason, raw: string, lang: string) {
super(false, target, 'speechToTextMessage');
this.connection = connection;
this.text = text;
this.reason = reason;
this.raw = raw;
this.lang = lang;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -1,113 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Publisher } from '../../OpenVidu/Publisher';
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { OpenViduLogger } from '../Logger/OpenViduLogger';
import { StreamEventReason } from './Types/Types';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* Triggered by:
* - `streamCreated` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamCreated) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamCreated) objects)
* - `streamDestroyed` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamDestroyed) and [Publisher](/en/stable/api/openvidu-browser/interfaces/PublisherEventMap.html#streamDestroyed) objects)
*/
export class StreamEvent extends Event {
/**
* Stream object that was created or destroyed
*/
stream: Stream;
/**
* For 'streamDestroyed' event:
* - "unpublish": method `Session.unpublish()` has been called
* - "disconnect": method `Session.disconnect()` has been called
* - "forceUnpublishByUser": some user has called `Session.forceUnpublish()` over the Stream
* - "forceDisconnectByUser": some user has called `Session.forceDisconnect()` over the Stream
* - "forceUnpublishByServer": the user's stream has been unpublished from the Session by the application
* - "forceDisconnectByServer": the user has been evicted from the Session by the application
* - "sessionClosedByServer": the Session has been closed by the application
* - "networkDisconnect": the user's network connection has dropped
* - "nodeCrashed": a node has crashed in the server side
*
* For 'streamCreated' empty string
*/
reason: StreamEventReason;
/**
* @hidden
*/
constructor(cancelable: boolean, target: Session | Publisher, type: string, stream: Stream, reason: StreamEventReason) {
super(cancelable, target, type);
this.stream = stream;
this.reason = reason;
}
/**
* @hidden
*/
callDefaultBehavior() {
if (this.type === 'streamDestroyed') {
if (this.target instanceof Session) {
// Remote Stream
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Session'");
this.stream.disposeWebRtcPeer();
} else if (this.target instanceof Publisher) {
// Local Stream
logger.info("Calling default behavior upon '" + this.type + "' event dispatched by 'Publisher'");
clearInterval((<Publisher>this.target).screenShareResizeInterval);
this.stream.isLocalStreamReadyToPublish = false;
// Delete Publisher object from OpenVidu publishers array
const openviduPublishers = (<Publisher>this.target).openvidu.publishers;
for (let i = 0; i < openviduPublishers.length; i++) {
if (openviduPublishers[i] === <Publisher>this.target) {
openviduPublishers.splice(i, 1);
break;
}
}
}
// Dispose the MediaStream local object
this.stream.disposeMediaStream();
// Remove from DOM all video elements associated to this Stream, if there's a StreamManager defined
// (method Session.subscribe must have been called)
if (this.stream.streamManager) this.stream.streamManager.removeAllVideos();
// Delete stream from Session.remoteStreamsCreated map
this.stream.session.remoteStreamsCreated.delete(this.stream.streamId);
// Delete StreamOptionsServer from remote Connection
const remoteConnection = this.stream.session.remoteConnections.get(this.stream.connection.connectionId);
if (!!remoteConnection && !!remoteConnection.remoteOptions) {
const streamOptionsServer = remoteConnection.remoteOptions.streams;
for (let i = streamOptionsServer.length - 1; i >= 0; --i) {
if (streamOptionsServer[i].id === this.stream.streamId) {
streamOptionsServer.splice(i, 1);
}
}
}
}
}
}

View File

@ -1,49 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Triggered by:
* - {@link StreamManagerEventMap.streamPlaying}
* - {@link StreamManagerEventMap.streamAudioVolumeChange}
*/
export class StreamManagerEvent extends Event {
/**
* For `streamAudioVolumeChange` event:
* - `{newValue: number, oldValue: number}`: new and old audio volume values. These values are between -100 (silence) and 0 (loudest possible volume).
* They are not exact and depend on how the browser is managing the audio track, but -100 and 0 can be taken as limit values.
*
* For `streamPlaying` event undefined
*/
value: Object | undefined;
/**
* @hidden
*/
constructor(target: StreamManager, type: string, value: Object | undefined) {
super(false, target, type);
this.value = value;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,83 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { Session } from '../../OpenVidu/Session';
import { Stream } from '../../OpenVidu/Stream';
import { StreamManager } from '../../OpenVidu/StreamManager';
import { StreamPropertyChangedEventReason, ChangedPropertyType } from './Types/Types';
/**
* Triggered by `streamPropertyChanged` (available for [Session](/en/stable/api/openvidu-browser/interfaces/SessionEventMap.html#streamPropertyChanged) and [StreamManager](/en/stable/api/openvidu-browser/interfaces/StreamManagerEventMap.html#streamPropertyChanged) objects)
*/
export class StreamPropertyChangedEvent extends Event {
/**
* The Stream whose property has changed. You can always identify the user publishing the changed stream by consulting property {@link Stream.connection}
*/
stream: Stream;
/**
* The property of the stream that changed. This value is either `"videoActive"`, `"audioActive"`, `"videoTrack"`, `"audioTrack"`, `"videoDimensions"` or `"filter"`
*/
changedProperty: ChangedPropertyType;
/**
* Cause of the change on the stream's property:
* - For `videoActive`: `"publishVideo"`
* - For `audioActive`: `"publishAudio"`
* - For `videoTrack`: `"trackReplaced"`
* - For `audioTrack`: `"trackReplaced"`
* - For `videoDimensions`: `"deviceRotated"`, `"screenResized"` or `"trackReplaced"`
* - For `filter`: `"applyFilter"`, `"execFilterMethod"` or `"removeFilter"`
*/
reason: StreamPropertyChangedEventReason;
/**
* New value of the property (after change, current value)
*/
newValue: Object;
/**
* Previous value of the property (before change)
*/
oldValue: Object;
/**
* @hidden
*/
constructor(
target: Session | StreamManager,
stream: Stream,
changedProperty: ChangedPropertyType,
newValue: Object,
oldValue: Object,
reason: StreamPropertyChangedEventReason
) {
super(false, target, 'streamPropertyChanged');
this.stream = stream;
this.changedProperty = changedProperty;
this.newValue = newValue;
this.oldValue = oldValue;
this.reason = reason;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() { }
}

View File

@ -1,42 +0,0 @@
export type ChangedPropertyType =
'videoActive' |
'audioActive' |
'videoTrack' |
'audioTrack' |
'videoDimensions' |
'filter';
export type StreamPropertyChangedEventReason =
'publishVideo' |
'publishAudio' |
'trackReplaced' |
'deviceRotated' |
'screenResized' |
'applyFilter' |
'execFilterMethod' |
'removeFilter';
export type ConnectionEventReason =
'disconnect' |
'forceDisconnectByUser' |
'forceDisconnectByServer' |
'sessionClosedByServer' |
'networkDisconnect' |
'nodeCrashed' |
'';
export type StreamEventReason =
ConnectionEventReason |
'unpublish' |
'forceUnpublishByUser' |
'forceUnpublishByServer';
export type RecordingEventReason =
'recordingStoppedByServer' |
'sessionClosedByServer' |
'automaticStop' |
'nodeCrashed';
export type SpeechToTextEventReason =
'recognizing' |
'recognized';

View File

@ -1,45 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Event } from './Event';
import { StreamManager } from '../../OpenVidu/StreamManager';
/**
* Triggered by:
* - {@link StreamManagerEventMap.videoElementCreated}
* - {@link StreamManagerEventMap.videoElementDestroyed}
*/
export class VideoElementEvent extends Event {
/**
* Video element that was created or destroyed
*/
element: HTMLVideoElement;
/**
* @hidden
*/
constructor(element: HTMLVideoElement, target: StreamManager, type: string) {
super(false, target, type);
this.element = element;
}
/**
* @hidden
*/
// tslint:disable-next-line:no-empty
callDefaultBehavior() {}
}

View File

@ -1,22 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export interface CustomMediaStreamConstraints {
constraints: MediaStreamConstraints;
audioTrack: MediaStreamTrack | undefined;
videoTrack: MediaStreamTrack | undefined;
}

View File

@ -1,21 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export interface IceServerProperties {
url: string;
username?: string;
credential?: string;
}

View File

@ -1,34 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Connection } from '../../../OpenVidu/Connection';
import { Filter } from '../../../OpenVidu/Filter';
import { TypeOfVideo } from '../../Enums/TypeOfVideo';
export interface InboundStreamOptions {
id: string;
createdAt: number;
connection: Connection;
hasAudio: boolean;
hasVideo: boolean;
audioActive: boolean;
videoActive: boolean;
typeOfVideo: TypeOfVideo;
frameRate: number;
videoDimensions: { width: number; height: number };
filter?: Filter;
}

View File

@ -1,42 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { RemoteConnectionOptions } from './RemoteConnectionOptions';
import { IceServerProperties } from './IceServerProperties';
export interface LocalConnectionOptions {
id: string;
finalUserId: string;
createdAt: number;
metadata: string;
value: RemoteConnectionOptions[];
session: string; // OpenVidu Session identifier
sessionId: string; // JSON-RPC session identifier
role: string;
record: boolean;
coturnIp: string;
coturnPort: number;
turnUsername: string;
turnCredential: string;
version: string;
mediaServer: string;
videoSimulcast: boolean;
life: number;
customIceServers?: IceServerProperties[];
recordingId?: string; // Defined if the session is being recorded and the client must be notified
recordingName?: string; // Defined if the session is being recorded and the client must be notified
}

View File

@ -1,23 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { PublisherProperties } from '../Public/PublisherProperties';
export interface OutboundStreamOptions {
publisherProperties: PublisherProperties;
mediaConstraints: MediaStreamConstraints;
}

View File

@ -1,25 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { StreamOptionsServer } from './StreamOptionsServer';
export interface RemoteConnectionOptions {
id: string;
createdAt: number;
metadata: string;
streams: StreamOptionsServer[];
}

View File

@ -1,22 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
export interface SessionOptions {
sessionId: string;
participantId: string;
metadata: string;
}

View File

@ -1,24 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Connection } from '../../../OpenVidu/Connection';
export interface SignalOptions {
type?: string;
to?: Connection[];
data?: string;
}

View File

@ -1,32 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Filter } from '../../../OpenVidu/Filter';
import { TypeOfVideo } from '../../Enums/TypeOfVideo';
export interface StreamOptionsServer {
id: string;
createdAt: number;
hasAudio: boolean;
hasVideo: boolean;
audioActive: boolean;
videoActive: boolean;
typeOfVideo: TypeOfVideo;
frameRate: number;
videoDimensions: string;
filter: Filter;
}

View File

@ -1,41 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* See {@link Session.capabilities}
*/
export interface Capabilities {
/**
* `true` if the client can call {@link Session.forceDisconnect}, `false` if not
*/
forceDisconnect: boolean;
/**
* `true` if the client can call {@link Session.forceUnpublish}, `false` if not
*/
forceUnpublish: boolean;
/**
* `true` if the client can call {@link Session.publish}, `false` if not
*/
publish: boolean;
/**
* `true` if the client can call {@link Session.subscribe}, `false` if not (true for every user for now)
*/
subscribe: boolean;
}

View File

@ -1,36 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* See {@link OpenVidu.getDevices}
*/
export interface Device {
/**
* The kind of device
*/
kind: 'videoinput' | 'audioinput';
/**
* Unique ID for the device. Use it on `audioSource` or `videoSource` properties of {@link PublisherProperties}
*/
deviceId: string;
/**
* Description of the device. An empty string if the user hasn't granted permissions to the site to access the device
*/
label: string;
}

View File

@ -1,79 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
/**
* See {@link OpenVidu.setAdvancedConfiguration}
*/
export interface OpenViduAdvancedConfiguration {
/**
* Array of [RTCIceServer](https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer) to be used by OpenVidu Browser. By default OpenVidu will generate the required credentials to use the COTURN server hosted along OpenVidu Server.
* You can also set this property to string 'freeice' to force the use of free STUN servers instead (got thanks to [freeice](https://github.com/DamonOehlman/freeice) library).
*
* > **WARNING**: this value has priority over the standard `iceServers` property of {@link rtcConfiguration}. It will override any value in `OpenViduAdvancedConfiguration.rtcConfiguration.iceServers`
*/
iceServers?: RTCIceServer[] | string;
/**
* Custom configuration for all [RTCPeerConnection](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/RTCPeerConnection#parameters) objects. This object will be passed as is to all RTCPeerConnection constructor (all Publishers and Subscribers).
*/
rtcConfiguration?: RTCConfiguration;
/**
* URL to a custom screen share extension for Chrome (always based on ours: [openvidu-screen-sharing-chrome-extension](https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension)) to be used instead of the default one.
* Must be something like this: `https://chrome.google.com/webstore/detail/YOUR_WEBSTORE_EXTENSION_NAME/YOUR_EXTENSION_ID`
*/
screenShareChromeExtension?: string;
/**
* Custom configuration for the {@link PublisherSpeakingEvent} feature and the [StreamManagerEvent.streamAudioVolumeChange](/en/stable/api/openvidu-browser/classes/StreamManagerEvent.html) feature. It is an object which includes the following optional properties:
* - `interval`: (number) how frequently the analyser polls the audio stream to check if speaking has started/stopped or audio volume has changed. Default **100** (ms)
* - `threshold`: (number) the volume at which _publisherStartSpeaking_ and _publisherStopSpeaking_ events will be fired. Default **-50** (dB)
*
* This sets the global default configuration that will affect all streams, but you can later customize these values for each specific stream by calling {@link StreamManager.updatePublisherSpeakingEventsOptions}
*/
publisherSpeakingEventsOptions?: {
interval?: number;
threshold?: number;
};
/**
* Determines the automatic reconnection process policy. Whenever the client's network drops, OpenVidu Browser starts a reconnection process with OpenVidu Server. After network is recovered, OpenVidu Browser automatically
* inspects all of its media streams to see their status. For any of them that are broken, it asks OpenVidu Server for a forced and silent reconnection.
*
* This policy is technically enough to recover any broken media connection after a network drop, but in practice it has been proven that OpenVidu Browser may think a media connection has properly recovered when in fact it has not.
* This is not a common case, but it may occur. This property allows **forcing OpenVidu Browser to reconnect all of its outgoing and incoming media streams** after a network drop regardless of their supposed status.
*
* Default to `false`.
*/
forceMediaReconnectionAfterNetworkDrop?: boolean;
/**
* The milliseconds that must elapse after triggering {@link ExceptionEvent} of name [`ICE_CONNECTION_DISCONNECTED`](/en/stable/api/openvidu-browser/enums/ExceptionEventName.html#ICE_CONNECTION_DISCONNECTED) to perform an automatic reconnection process of the affected media stream.
* This automatic reconnection process can only take place if the client still has network connection to OpenVidu Server. If the ICE connection has broken because of a total network drop,
* then no reconnection process will be possible at all.
*
* Default to `4000`.
*/
iceConnectionDisconnectedExceptionTimeout?: number;
/**
* The milliseconds that must elapse for the {@link ExceptionEvent} of name [`NO_STREAM_PLAYING_EVENT`](/en/stable/api/openvidu-browser/enums/ExceptionEventName.html#NO_STREAM_PLAYING_EVENT) to be fired.
*
* Default to `4000`.
*/
noStreamPlayingEventExceptionTimeout?: number;
}

View File

@ -1,101 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Filter } from '../../../OpenVidu/Filter';
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
/**
* See {@link OpenVidu.initPublisher}
*/
export interface PublisherProperties {
/**
* Which device should provide the audio source. Can be:
* - Property `deviceId` of a {@link Device}
* - `"screen"` to share the screen audio when {@link videoSource} is set to `"screen"`. If {@link videoSource} is not set to `"screen"` this will result in no audio source and a video-only publisher.
* - A MediaStreamTrack obtained from a MediaStream object with {@link OpenVidu.getUserMedia}
* - `false` or null to have a video-only publisher
* @default _Default microphone_
*/
audioSource?: string | MediaStreamTrack | boolean;
/**
* Desired framerate of the video in frames per second.
* Limiting the framerate has always effect on browsers Chrome and Opera. Firefox requires that the input device explicitly supports the desired framerate.
* @default undefined
*/
frameRate?: number;
/**
* How the video element of the publisher should be inserted in the DOM
* @default VideoInsertMode.APPEND
*/
insertMode?: VideoInsertMode | string;
/**
* Whether the publisher's video will be mirrored in the page or not. Only affects the local view of the publisher in the browser (remote streams will not be mirrored). If `videoSource` is set to "screen" this property is fixed to `false`
* @default true
*/
mirror?: boolean;
/**
* Whether to initially publish to the session with the audio unmuted or muted. Only makes sense if property `audioSource` is NOT set to *false* or *null*. You can change the audio state later during the session with {@link Publisher.publishAudio}
* @default true
*/
publishAudio?: boolean;
/**
* Whether to initially publish to the session with the video enabled or disabled. Only makes sense if property `videoSource` is NOT set to *false* or *null*. You can change the video state later during the session with {@link Publisher.publishVideo}
* @default true
*/
publishVideo?: boolean;
/**
* Resolution of the video: `"320x240"`, `"640x480"`, `"1280x720"` (low, medium and high quality respectively)
* @default "640x480"
*/
resolution?: string;
/**
* Which device should provide the video source. Can be:
* - Property `deviceId` of a {@link Device}
* - `"screen"` to screen-share. We provide a default screen-shraring extension for Chrome that can run in any domain, but you can customize it so it has your own icon, your own name, etc. Visit this
* [GitHub repository](https://github.com/OpenVidu/openvidu-screen-sharing-chrome-extension/) to learn how. Once you have uploaded your own extension to Chrome Web Store,
* simply call `OpenVidu.setAdvancedConfiguration({screenShareChromeExtension : "https://chrome.google.com/webstore/detail/YOUR_EXTENSION_NAME/YOUR_EXTENSION_ID"})` before calling `OpenVidu.initPublisher(targetElement, {videoSource: "screen"})`.
* For Firefox (<66) `"screen"` string will ask for permissions to share the entire screen. To ask for a specific window or application, use `"window"` string instead (this only applies to Firefox).
* - A MediaStreamTrack obtained from a MediaStream object with {@link OpenVidu.getUserMedia}
* - `false` or null to have an audio-only publisher
* @default _Default camera_
*/
videoSource?: string | MediaStreamTrack | boolean;
/**
* Use Simulcast video on WebRTC Publishers.
* Senders will encode duplicate video streams with different qualities,
* so the media server is able to select the most appropriate quality stream
* for each Subscriber.
* This setting is honored only if OpenVidu Server was configured to use the
* mediasoup media server. Otherwise, Simulcast will be disabled.
*/
videoSimulcast?: boolean;
/**
* **WARNING**: experimental option. This property may change in the near future
*
* Define a filter to apply in the Publisher's stream
*/
filter?: Filter;
}

View File

@ -1,40 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { Connection } from '../../../OpenVidu/Connection';
/**
* See {@link Session.signal}
*/
export interface SignalOptions {
/**
* The actual message of the signal.
*/
data?: string;
/**
* The participants to whom to send the signal. They will only receive it if they are subscribed to
* event `Session.on('signal')`. If empty or undefined, the signal will be send to all participants.
*/
to?: Connection[];
/**
* The type of the signal. Participants subscribed to event `Session.on('signal:type')` will
* receive it. Participants subscribed to `Session.on('signal')` will receive all signals.
*/
type?: string;
}

View File

@ -1,57 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
export interface StreamManagerVideo {
/**
* DOM video element displaying the StreamManager's stream
*/
video: HTMLVideoElement;
/**
* `id` attribute of the DOM video element displaying the StreamManager's stream
*/
id: string;
/**
* The DOM HTMLElement assigned as target element when creating a video for the StreamManager. This property is defined when:
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing a valid `targetElement` parameter.
* - {@link StreamManager.createVideoElement} has been called.
*
* This property is undefined when:
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - {@link StreamManager.addVideoElement} has been called.
*/
targetElement?: HTMLElement;
/**
* How the DOM video element should be inserted with respect to `targetElement`. This property is defined when:
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing a valid `targetElement` parameter.
* - {@link StreamManager.createVideoElement} has been called.
*
* This property is undefined when:
* - {@link OpenVidu.initPublisher} or {@link Session.subscribe} methods have been called passing *null* or *undefined* as `targetElement` parameter.
* - {@link StreamManager.addVideoElement} has been called.
*/
insertMode?: VideoInsertMode;
/**
* @hidden
*/
canplayListenerAdded: boolean;
}

View File

@ -1,41 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import { VideoInsertMode } from '../../Enums/VideoInsertMode';
/**
* See {@link Session.subscribe}
*/
export interface SubscriberProperties {
/**
* How the video element of the subscriber should be inserted in the DOM
* @default VideoInsertMode.APPEND
*/
insertMode?: VideoInsertMode | string;
/**
* Whether to initially subscribe to the audio track of the stream or not. You can change the audio state later with {@link Subscriber.subscribeToAudio}
* @default true
*/
subscribeToAudio?: boolean;
/**
* Whether to initially subscribe to the video track of the stream or not. You can change the video state later with {@link Subscriber.subscribeToVideo}
* @default true
*/
subscribeToVideo?: boolean;
}

View File

@ -1,52 +0,0 @@
function Mapper() {
var sources = {};
this.forEach = function (callback) {
for (var key in sources) {
var source = sources[key];
for (var key2 in source) callback(source[key2]);
}
};
this.get = function (id, source) {
var ids = sources[source];
if (ids == undefined) return undefined;
return ids[id];
};
this.remove = function (id, source) {
var ids = sources[source];
if (ids == undefined) return;
delete ids[id];
// Check it's empty
for (var i in ids) {
return false;
}
delete sources[source];
};
this.set = function (value, id, source) {
if (value == undefined) return this.remove(id, source);
var ids = sources[source];
if (ids == undefined) sources[source] = ids = {};
ids[id] = value;
};
}
Mapper.prototype.pop = function (id, source) {
var value = this.get(id, source);
if (value == undefined) return undefined;
this.remove(id, source);
return value;
};
module.exports = Mapper;

View File

@ -1,20 +0,0 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var JsonRpcClient = require('./jsonrpcclient');
exports.JsonRpcClient = JsonRpcClient;

View File

@ -1,280 +0,0 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var RpcBuilder = require('../');
var WebSocketWithReconnection = require('./transports/webSocketWithReconnection');
var OpenViduLogger = require('../../../Logger/OpenViduLogger').OpenViduLogger;
Date.now =
Date.now ||
function () {
return +new Date();
};
var PING_INTERVAL = 5000;
var RECONNECTING = 'RECONNECTING';
var CONNECTED = 'CONNECTED';
var DISCONNECTED = 'DISCONNECTED';
var Logger = OpenViduLogger.getInstance();
/**
*
* heartbeat: interval in ms for each heartbeat message,
* <pre>
* ws : {
* uri : URI to conntect to,
* onconnected : callback method to invoke when connection is successful,
* ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached),
* onreconnecting : callback method to invoke when the client is reconnecting,
* onreconnected : callback method to invoke when the client successfully reconnects,
* onerror : callback method to invoke when there is an error
* },
* rpc : {
* requestTimeout : timeout for a request,
* sessionStatusChanged: callback method for changes in session status,
* mediaRenegotiation: mediaRenegotiation
* }
* </pre>
*/
function JsonRpcClient(configuration) {
var self = this;
var wsConfig = configuration.ws;
var notReconnectIfNumLessThan = -1;
var pingNextNum = 0;
var enabledPings = true;
var pingPongStarted = false;
var pingInterval;
var status = DISCONNECTED;
var onreconnecting = wsConfig.onreconnecting;
var onreconnected = wsConfig.onreconnected;
var onconnected = wsConfig.onconnected;
var onerror = wsConfig.onerror;
configuration.rpc.pull = function (params, request) {
request.reply(null, 'push');
};
wsConfig.onreconnecting = function () {
Logger.debug('--------- ONRECONNECTING -----------');
if (status === RECONNECTING) {
Logger.error('Websocket already in RECONNECTING state when receiving a new ONRECONNECTING message. Ignoring it');
return;
}
stopPing();
status = RECONNECTING;
if (onreconnecting) {
onreconnecting();
}
};
wsConfig.onreconnected = function () {
Logger.debug('--------- ONRECONNECTED -----------');
if (status === CONNECTED) {
Logger.error('Websocket already in CONNECTED state when receiving a new ONRECONNECTED message. Ignoring it');
return;
}
status = CONNECTED;
updateNotReconnectIfLessThan();
if (onreconnected) {
onreconnected();
}
};
wsConfig.onconnected = function () {
Logger.debug('--------- ONCONNECTED -----------');
if (status === CONNECTED) {
Logger.error('Websocket already in CONNECTED state when receiving a new ONCONNECTED message. Ignoring it');
return;
}
status = CONNECTED;
enabledPings = true;
usePing();
if (onconnected) {
onconnected();
}
};
wsConfig.onerror = function (error) {
Logger.debug('--------- ONERROR -----------');
status = DISCONNECTED;
stopPing();
if (onerror) {
onerror(error);
}
};
var ws = new WebSocketWithReconnection(wsConfig);
Logger.debug('Connecting websocket to URI: ' + wsConfig.uri);
var rpcBuilderOptions = {
request_timeout: configuration.rpc.requestTimeout,
ping_request_timeout: configuration.rpc.heartbeatRequestTimeout
};
var rpc = new RpcBuilder(RpcBuilder.packers.JsonRPC, rpcBuilderOptions, ws, function (request) {
Logger.debug('Received request: ' + JSON.stringify(request));
try {
var func = configuration.rpc[request.method];
if (func === undefined) {
Logger.error('Method ' + request.method + ' not registered in client');
} else {
func(request.params, request);
}
} catch (err) {
Logger.error('Exception processing request: ' + JSON.stringify(request));
Logger.error(err);
}
});
this.send = function (method, params, callback) {
var requestTime = Date.now();
rpc.encode(method, params, function (error, result) {
if (error) {
try {
Logger.error(
'ERROR:' +
error.message +
' in Request: method:' +
method +
' params:' +
JSON.stringify(params) +
' request:' +
error.request
);
if (error.data) {
Logger.error('ERROR DATA:' + JSON.stringify(error.data));
}
} catch (e) {}
error.requestTime = requestTime;
}
if (callback) {
if (result != undefined && result.value !== 'pong') {
Logger.debug('Response: ' + JSON.stringify(result));
}
callback(error, result);
}
});
};
function updateNotReconnectIfLessThan() {
Logger.debug('notReconnectIfNumLessThan = ' + pingNextNum + ' (old=' + notReconnectIfNumLessThan + ')');
notReconnectIfNumLessThan = pingNextNum;
}
function sendPing() {
if (enabledPings) {
var params = null;
if (pingNextNum == 0 || pingNextNum == notReconnectIfNumLessThan) {
params = {
interval: configuration.heartbeat || PING_INTERVAL
};
}
pingNextNum++;
self.send(
'ping',
params,
(function (pingNum) {
return function (error, result) {
if (error) {
Logger.debug('Error in ping request #' + pingNum + ' (' + error.message + ')');
if (pingNum > notReconnectIfNumLessThan) {
enabledPings = false;
updateNotReconnectIfLessThan();
Logger.debug('Server did not respond to ping message #' + pingNum + '. Reconnecting... ');
ws.reconnectWs();
}
}
};
})(pingNextNum)
);
} else {
Logger.debug('Trying to send ping, but ping is not enabled');
}
}
/*
* If configuration.hearbeat has any value, the ping-pong will work with the interval
* of configuration.hearbeat
*/
function usePing() {
if (!pingPongStarted) {
Logger.debug('Starting ping (if configured)');
pingPongStarted = true;
if (configuration.heartbeat != undefined) {
pingInterval = setInterval(sendPing, configuration.heartbeat);
sendPing();
}
}
}
function stopPing() {
clearInterval(pingInterval);
pingPongStarted = false;
enabledPings = false;
pingNextNum = -1;
rpc.cancel();
}
this.close = function (code, reason) {
Logger.debug('Closing with code: ' + code + ' because: ' + reason);
if (pingInterval != undefined) {
Logger.debug('Clearing ping interval');
clearInterval(pingInterval);
}
pingPongStarted = false;
enabledPings = false;
ws.close(code, reason);
};
this.reconnect = function () {
ws.reconnectWs();
};
this.resetPing = function () {
enabledPings = true;
pingNextNum = 0;
usePing();
};
this.getReadyState = function () {
return ws.getReadyState();
};
}
module.exports = JsonRpcClient;

View File

@ -1,20 +0,0 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var WebSocketWithReconnection = require('./webSocketWithReconnection');
exports.WebSocketWithReconnection = WebSocketWithReconnection;

View File

@ -1,161 +0,0 @@
/*
* (C) Copyright 2013-2015 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
'use strict';
var OpenViduLogger = require('../../../../Logger/OpenViduLogger').OpenViduLogger;
var Logger = OpenViduLogger.getInstance();
var MAX_RETRIES = 2000; // Forever...
var RETRY_TIME_MS = 3000; // FIXME: Implement exponential wait times...
var CONNECTING = 0;
var OPEN = 1;
var CLOSING = 2;
var CLOSED = 3;
/*
config = {
uri : wsUri,
onconnected : callback method to invoke when connection is successful,
ondisconnect : callback method to invoke when the connection is lost (max retries for reconnecting reached),
onreconnecting : callback method to invoke when the client is reconnecting,
onreconnected : callback method to invoke when the client successfully reconnects,
};
*/
function WebSocketWithReconnection(config) {
var closing = false;
var registerMessageHandler;
var wsUri = config.uri;
var reconnecting = false;
var ws = new WebSocket(wsUri);
ws.onopen = () => {
Logger.debug('WebSocket connected to ' + wsUri);
if (config.onconnected) {
config.onconnected();
}
};
ws.onerror = (error) => {
Logger.error('Could not connect to ' + wsUri + ' (invoking onerror if defined)', error);
if (config.onerror) {
config.onerror(error);
}
};
var reconnectionOnClose = () => {
if (ws.readyState === CLOSED) {
if (closing) {
Logger.debug('Connection closed by user');
} else {
if (config.ismasternodecrashed()) {
Logger.error('Master Node has crashed. Stopping reconnection process');
} else {
Logger.debug('Connection closed unexpectedly. Reconnecting...');
reconnect(MAX_RETRIES, 1);
}
}
} else {
Logger.debug('Close callback from previous websocket. Ignoring it');
}
};
ws.onclose = reconnectionOnClose;
function reconnect(maxRetries, numRetries) {
Logger.debug('reconnect (attempt #' + numRetries + ', max=' + maxRetries + ')');
if (numRetries === 1) {
if (reconnecting) {
Logger.warn('Trying to reconnect when already reconnecting... Ignoring this reconnection.');
return;
} else {
reconnecting = true;
}
if (config.onreconnecting) {
config.onreconnecting();
}
}
reconnectAux(maxRetries, numRetries);
}
function addReconnectionQueryParamsIfMissing(uriString) {
var searchParams = new URLSearchParams(new URL(uriString).search);
if (!searchParams.has('reconnect')) {
uriString = Array.from(searchParams).length > 0 ? uriString + '&reconnect=true' : uriString + '?reconnect=true';
}
return uriString;
}
function reconnectAux(maxRetries, numRetries) {
Logger.debug('Reconnection attempt #' + numRetries);
ws.close(4104, 'Connection closed for reconnection');
wsUri = addReconnectionQueryParamsIfMissing(wsUri);
ws = new WebSocket(wsUri);
ws.onopen = () => {
Logger.debug('Reconnected to ' + wsUri + ' after ' + numRetries + ' attempts...');
reconnecting = false;
registerMessageHandler();
if (config.onreconnected) {
config.onreconnected();
}
ws.onclose = reconnectionOnClose;
};
ws.onerror = (error) => {
Logger.warn('Reconnection error: ', error);
if (numRetries === maxRetries) {
if (config.ondisconnect) {
config.ondisconnect();
}
} else {
setTimeout(() => {
reconnect(maxRetries, numRetries + 1);
}, RETRY_TIME_MS);
}
};
}
this.close = (code, reason) => {
closing = true;
ws.close(code, reason);
};
this.reconnectWs = () => {
Logger.debug('reconnectWs');
reconnect(MAX_RETRIES, 1);
};
this.send = (message) => {
ws.send(message);
};
this.addEventListener = (type, callback) => {
registerMessageHandler = () => {
ws.addEventListener(type, callback);
};
registerMessageHandler();
};
this.getReadyState = () => {
return ws.readyState;
};
}
module.exports = WebSocketWithReconnection;

View File

@ -1,687 +0,0 @@
/*
* (C) Copyright 2014 Kurento (http://kurento.org/)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
var defineProperty_IE8 = false;
if (Object.defineProperty) {
try {
Object.defineProperty({}, 'x', {});
} catch (e) {
defineProperty_IE8 = true;
}
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== 'function') {
// closest thing possible to the ECMAScript 5
// internal IsCallable function
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP && oThis ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
var EventEmitter = require('events').EventEmitter;
var inherits = require('inherits');
var packers = require('./packers');
var Mapper = require('./Mapper');
var BASE_TIMEOUT = 5000;
function unifyResponseMethods(responseMethods) {
if (!responseMethods) return {};
for (var key in responseMethods) {
var value = responseMethods[key];
if (typeof value == 'string')
responseMethods[key] = {
response: value
};
}
return responseMethods;
}
function unifyTransport(transport) {
if (!transport) return;
// Transport as a function
if (transport instanceof Function)
return {
send: transport
};
// WebSocket & DataChannel
if (transport.send instanceof Function) return transport;
// Message API (Inter-window & WebWorker)
if (transport.postMessage instanceof Function) {
transport.send = transport.postMessage;
return transport;
}
// Stream API
if (transport.write instanceof Function) {
transport.send = transport.write;
return transport;
}
// Transports that only can receive messages, but not send
if (transport.onmessage !== undefined) return;
if (transport.pause instanceof Function) return;
throw new SyntaxError('Transport is not a function nor a valid object');
}
/**
* Representation of a RPC notification
*
* @class
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
*/
function RpcNotification(method, params) {
if (defineProperty_IE8) {
this.method = method;
this.params = params;
} else {
Object.defineProperty(this, 'method', {
value: method,
enumerable: true
});
Object.defineProperty(this, 'params', {
value: params,
enumerable: true
});
}
}
/**
* @class
*
* @constructor
*
* @param {object} packer
*
* @param {object} [options]
*
* @param {object} [transport]
*
* @param {Function} [onRequest]
*/
function RpcBuilder(packer, options, transport, onRequest) {
var self = this;
if (!packer) throw new SyntaxError('Packer is not defined');
if (!packer.pack || !packer.unpack) throw new SyntaxError('Packer is invalid');
var responseMethods = unifyResponseMethods(packer.responseMethods);
if (options instanceof Function) {
if (transport != undefined) throw new SyntaxError("There can't be parameters after onRequest");
onRequest = options;
transport = undefined;
options = undefined;
}
if (options && options.send instanceof Function) {
if (transport && !(transport instanceof Function)) throw new SyntaxError('Only a function can be after transport');
onRequest = transport;
transport = options;
options = undefined;
}
if (transport instanceof Function) {
if (onRequest != undefined) throw new SyntaxError("There can't be parameters after onRequest");
onRequest = transport;
transport = undefined;
}
if (transport && transport.send instanceof Function)
if (onRequest && !(onRequest instanceof Function)) throw new SyntaxError('Only a function can be after transport');
options = options || {};
EventEmitter.call(this);
if (onRequest) this.on('request', onRequest);
if (defineProperty_IE8) this.peerID = options.peerID;
else
Object.defineProperty(this, 'peerID', {
value: options.peerID
});
var max_retries = options.max_retries || 0;
function transportMessage(event) {
self.decode(event.data || event);
}
this.getTransport = function () {
return transport;
};
this.setTransport = function (value) {
// Remove listener from old transport
if (transport) {
// W3C transports
if (transport.removeEventListener) transport.removeEventListener('message', transportMessage);
// Node.js Streams API
else if (transport.removeListener) transport.removeListener('data', transportMessage);
}
// Set listener on new transport
if (value) {
// W3C transports
if (value.addEventListener) value.addEventListener('message', transportMessage);
// Node.js Streams API
else if (value.addListener) value.addListener('data', transportMessage);
}
transport = unifyTransport(value);
};
if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', {
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
});
this.setTransport(transport);
var request_timeout = options.request_timeout || BASE_TIMEOUT;
var ping_request_timeout = options.ping_request_timeout || request_timeout;
var response_timeout = options.response_timeout || BASE_TIMEOUT;
var duplicates_timeout = options.duplicates_timeout || BASE_TIMEOUT;
var requestID = 0;
var requests = new Mapper();
var responses = new Mapper();
var processedResponses = new Mapper();
var message2Key = {};
/**
* Store the response to prevent to process duplicate request later
*/
function storeResponse(message, id, dest) {
var response = {
message: message,
/** Timeout to auto-clean old responses */
timeout: setTimeout(function () {
responses.remove(id, dest);
}, response_timeout)
};
responses.set(response, id, dest);
}
/**
* Store the response to ignore duplicated messages later
*/
function storeProcessedResponse(ack, from) {
var timeout = setTimeout(function () {
processedResponses.remove(ack, from);
}, duplicates_timeout);
processedResponses.set(timeout, ack, from);
}
/**
* Representation of a RPC request
*
* @class
* @extends RpcNotification
*
* @constructor
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param {Integer} id - identifier of the request
* @param [from] - source of the notification
*/
function RpcRequest(method, params, id, from, transport) {
RpcNotification.call(this, method, params);
this.getTransport = function () {
return transport;
};
this.setTransport = function (value) {
transport = unifyTransport(value);
};
if (!defineProperty_IE8)
Object.defineProperty(this, 'transport', {
get: this.getTransport.bind(this),
set: this.setTransport.bind(this)
});
var response = responses.get(id, from);
/**
* @constant {Boolean} duplicated
*/
if (!(transport || self.getTransport())) {
if (defineProperty_IE8) this.duplicated = Boolean(response);
else
Object.defineProperty(this, 'duplicated', {
value: Boolean(response)
});
}
var responseMethod = responseMethods[method];
this.pack = packer.pack.bind(packer, this, id);
/**
* Generate a response to this request
*
* @param {Error} [error]
* @param {*} [result]
*
* @returns {string}
*/
this.reply = function (error, result, transport) {
// Fix optional parameters
if (error instanceof Function || (error && error.send instanceof Function)) {
if (result != undefined) throw new SyntaxError("There can't be parameters after callback");
transport = error;
result = null;
error = undefined;
} else if (result instanceof Function || (result && result.send instanceof Function)) {
if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
transport = result;
result = null;
}
transport = unifyTransport(transport);
// Duplicated request, remove old response timeout
if (response) clearTimeout(response.timeout);
if (from != undefined) {
if (error) error.dest = from;
if (result) result.dest = from;
}
var message;
// New request or overriden one, create new response with provided data
if (error || result != undefined) {
if (self.peerID != undefined) {
if (error) error.from = self.peerID;
else result.from = self.peerID;
}
// Protocol indicates that responses has own request methods
if (responseMethod) {
if (responseMethod.error == undefined && error)
message = {
error: error
};
else {
var method = error ? responseMethod.error : responseMethod.response;
message = {
method: method,
params: error || result
};
}
} else
message = {
error: error,
result: result
};
message = packer.pack(message, id);
}
// Duplicate & not-overriden request, re-send old response
else if (response) message = response.message;
// New empty reply, response null value
else
message = packer.pack(
{
result: null
},
id
);
// Store the response to prevent to process a duplicated request later
storeResponse(message, id, from);
// Return the stored response so it can be directly send back
transport = transport || this.getTransport() || self.getTransport();
if (transport) return transport.send(message);
return message;
};
}
inherits(RpcRequest, RpcNotification);
function cancel(message) {
var key = message2Key[message];
if (!key) return;
delete message2Key[message];
var request = requests.pop(key.id, key.dest);
if (!request) return;
clearTimeout(request.timeout);
// Start duplicated responses timeout
storeProcessedResponse(key.id, key.dest);
}
/**
* Allow to cancel a request and don't wait for a response
*
* If `message` is not given, cancel all the request
*/
this.cancel = function (message) {
if (message) return cancel(message);
for (var message in message2Key) cancel(message);
};
this.close = function () {
// Prevent to receive new messages
var transport = this.getTransport();
if (transport && transport.close) transport.close(4003, 'Cancel request');
// Request & processed responses
this.cancel();
processedResponses.forEach(clearTimeout);
// Responses
responses.forEach(function (response) {
clearTimeout(response.timeout);
});
};
/**
* Generates and encode a JsonRPC 2.0 message
*
* @param {String} method -method of the notification
* @param params - parameters of the notification
* @param [dest] - destination of the notification
* @param {object} [transport] - transport where to send the message
* @param [callback] - function called when a response to this request is
* received. If not defined, a notification will be send instead
*
* @returns {string} A raw JsonRPC 2.0 request or notification string
*/
this.encode = function (method, params, dest, transport, callback) {
// Fix optional parameters
if (params instanceof Function) {
if (dest != undefined) throw new SyntaxError("There can't be parameters after callback");
callback = params;
transport = undefined;
dest = undefined;
params = undefined;
} else if (dest instanceof Function) {
if (transport != undefined) throw new SyntaxError("There can't be parameters after callback");
callback = dest;
transport = undefined;
dest = undefined;
} else if (transport instanceof Function) {
if (callback != undefined) throw new SyntaxError("There can't be parameters after callback");
callback = transport;
transport = undefined;
}
if (self.peerID != undefined) {
params = params || {};
params.from = self.peerID;
}
if (dest != undefined) {
params = params || {};
params.dest = dest;
}
// Encode message
var message = {
method: method,
params: params
};
if (callback) {
var id = requestID++;
var retried = 0;
message = packer.pack(message, id);
function dispatchCallback(error, result) {
self.cancel(message);
callback(error, result);
}
var request = {
message: message,
callback: dispatchCallback,
responseMethods: responseMethods[method] || {}
};
var encode_transport = unifyTransport(transport);
function sendRequest(transport) {
var rt = method === 'ping' ? ping_request_timeout : request_timeout;
request.timeout = setTimeout(timeout, rt * Math.pow(2, retried++));
message2Key[message] = {
id: id,
dest: dest
};
requests.set(request, id, dest);
transport = transport || encode_transport || self.getTransport();
if (transport) return transport.send(message);
return message;
}
function retry(transport) {
transport = unifyTransport(transport);
console.warn(retried + ' retry for request message:', message);
var timeout = processedResponses.pop(id, dest);
clearTimeout(timeout);
return sendRequest(transport);
}
function timeout() {
if (retried < max_retries) return retry(transport);
var error = new Error('Request has timed out');
error.request = message;
error.retry = retry;
dispatchCallback(error);
}
return sendRequest(transport);
}
// Return the packed message
message = packer.pack(message);
transport = transport || this.getTransport();
if (transport) return transport.send(message);
return message;
};
/**
* Decode and process a JsonRPC 2.0 message
*
* @param {string} message - string with the content of the message
*
* @returns {RpcNotification|RpcRequest|undefined} - the representation of the
* notification or the request. If a response was processed, it will return
* `undefined` to notify that it was processed
*
* @throws {TypeError} - Message is not defined
*/
this.decode = function (message, transport) {
if (!message) throw new TypeError('Message is not defined');
try {
message = packer.unpack(message);
} catch (e) {
// Ignore invalid messages
return console.debug(e, message);
}
var id = message.id;
var ack = message.ack;
var method = message.method;
var params = message.params || {};
var from = params.from;
var dest = params.dest;
// Ignore messages send by us
if (self.peerID != undefined && from == self.peerID) return;
// Notification
if (id == undefined && ack == undefined) {
var notification = new RpcNotification(method, params);
if (self.emit('request', notification)) return;
return notification;
}
function processRequest() {
// If we have a transport and it's a duplicated request, reply inmediatly
transport = unifyTransport(transport) || self.getTransport();
if (transport) {
var response = responses.get(id, from);
if (response) return transport.send(response.message);
}
var idAck = id != undefined ? id : ack;
var request = new RpcRequest(method, params, idAck, from, transport);
if (self.emit('request', request)) return;
return request;
}
function processResponse(request, error, result) {
request.callback(error, result);
}
function duplicatedResponse(timeout) {
console.warn('Response already processed', message);
// Update duplicated responses timeout
clearTimeout(timeout);
storeProcessedResponse(ack, from);
}
// Request, or response with own method
if (method) {
// Check if it's a response with own method
if (dest == undefined || dest == self.peerID) {
var request = requests.get(ack, from);
if (request) {
var responseMethods = request.responseMethods;
if (method == responseMethods.error) return processResponse(request, params);
if (method == responseMethods.response) return processResponse(request, null, params);
return processRequest();
}
var processed = processedResponses.get(ack, from);
if (processed) return duplicatedResponse(processed);
}
// Request
return processRequest();
}
var error = message.error;
var result = message.result;
// Ignore responses not send to us
if (error && error.dest && error.dest != self.peerID) return;
if (result && result.dest && result.dest != self.peerID) return;
// Response
var request = requests.get(ack, from);
if (!request) {
var processed = processedResponses.get(ack, from);
if (processed) return duplicatedResponse(processed);
return console.warn('No callback was defined for this message', message);
}
// Process response
processResponse(request, error, result);
};
}
inherits(RpcBuilder, EventEmitter);
RpcBuilder.RpcNotification = RpcNotification;
module.exports = RpcBuilder;
var clients = require('./clients');
var transports = require('./clients/transports');
RpcBuilder.clients = clients;
RpcBuilder.clients.transports = transports;
RpcBuilder.packers = packers;

View File

@ -1,85 +0,0 @@
/**
* JsonRPC 2.0 packer
*/
/**
* Pack a JsonRPC 2.0 message
*
* @param {Object} message - object to be packaged. It requires to have all the
* fields needed by the JsonRPC 2.0 message that it's going to be generated
*
* @return {String} - the stringified JsonRPC 2.0 message
*/
function pack(message, id) {
var result = {
jsonrpc: '2.0'
};
// Request
if (message.method) {
result.method = message.method;
if (message.params) result.params = message.params;
// Request is a notification
if (id != undefined) result.id = id;
}
// Response
else if (id != undefined) {
if (message.error) {
if (message.result !== undefined) throw new TypeError('Both result and error are defined');
result.error = message.error;
} else if (message.result !== undefined) result.result = message.result;
else throw new TypeError('No result or error is defined');
result.id = id;
}
return JSON.stringify(result);
}
/**
* Unpack a JsonRPC 2.0 message
*
* @param {String} message - string with the content of the JsonRPC 2.0 message
*
* @throws {TypeError} - Invalid JsonRPC version
*
* @return {Object} - object filled with the JsonRPC 2.0 message content
*/
function unpack(message) {
var result = message;
if (typeof message === 'string' || message instanceof String) {
result = JSON.parse(message);
}
// Check if it's a valid message
var version = result.jsonrpc;
if (version !== '2.0') throw new TypeError("Invalid JsonRPC version '" + version + "': " + message);
// Response
if (result.method == undefined) {
if (result.id == undefined) throw new TypeError('Invalid message: ' + message);
var result_defined = result.result !== undefined;
var error_defined = result.error !== undefined;
// Check only result or error is defined, not both or none
if (result_defined && error_defined) throw new TypeError('Both result and error are defined: ' + message);
if (!result_defined && !error_defined) throw new TypeError('No result or error is defined: ' + message);
result.ack = result.id;
delete result.id;
}
// Return unpacked message
return result;
}
exports.pack = pack;
exports.unpack = unpack;

View File

@ -1,10 +0,0 @@
function pack(message) {
throw new TypeError('Not yet implemented');
}
function unpack(message) {
throw new TypeError('Not yet implemented');
}
exports.pack = pack;
exports.unpack = unpack;

View File

@ -1,5 +0,0 @@
var JsonRPC = require('./JsonRPC');
var XmlRPC = require('./XmlRPC');
exports.JsonRPC = JsonRPC;
exports.XmlRPC = XmlRPC;

View File

@ -1,41 +0,0 @@
type ConsoleFunction = (...data: any) => void;
export class ConsoleLogger {
/**
* @hidden
*/
logger: Console;
/**
* @hidden
*/
log: ConsoleFunction;
/**
* @hidden
*/
info: ConsoleFunction;
/**
* @hidden
*/
debug: ConsoleFunction;
/**
* @hidden
*/
warn: ConsoleFunction;
/**
* @hidden
*/
error: ConsoleFunction;
constructor(console: Console) {
this.logger = console;
(this.log = console.log),
(this.info = console.info),
(this.debug = console.debug),
(this.warn = console.warn),
(this.error = console.error);
}
}

View File

@ -1,285 +0,0 @@
import { JL } from 'jsnlog';
import { OpenVidu } from '../../OpenVidu/OpenVidu';
import { ConsoleLogger } from './ConsoleLogger';
import { OpenViduLoggerConfiguration } from './OpenViduLoggerConfiguration';
export class OpenViduLogger {
private static instance: OpenViduLogger;
private JSNLOG_URL: string = '/openvidu/elk/openvidu-browser-logs';
private MAX_JSNLOG_BATCH_LOG_MESSAGES: number = 100;
private MAX_MSECONDS_BATCH_MESSAGES: number = 5000;
private MAX_LENGTH_STRING_JSON: number = 1000;
private defaultConsoleLogger: ConsoleLogger = new ConsoleLogger(globalThis.console);
private currentAppender: any;
private isProdMode = false;
private isJSNLogSetup = false;
// This two variables are used to restart JSNLog
// on different sessions and different userIds
private loggingSessionId: string | undefined;
/**
* @hidden
*/
static configureJSNLog(openVidu: OpenVidu, token: string) {
try {
// If dev mode or...
if (
globalThis['LOG_JSNLOG_RESULTS'] ||
// If instance is created and it is OpenVidu Pro
(this.instance &&
openVidu.isAtLeastPro &&
// If logs are enabled
this.instance.isOpenViduBrowserLogsDebugActive(openVidu) &&
// Only reconfigure it if session or finalUserId has changed
this.instance.canConfigureJSNLog(openVidu, this.instance))
) {
// Check if app logs can be sent
// and replace console.log function to send
// logs of the application
if (openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app) {
this.instance.replaceWindowConsole();
}
// isJSNLogSetup will not be true until completed setup
this.instance.isJSNLogSetup = false;
this.instance.info('Configuring JSNLogs.');
const finalUserId = openVidu.finalUserId;
const sessionId = openVidu.session.sessionId;
const beforeSendCallback = (xhr) => {
// If 401 or 403 or 404 modify ready and status so JSNLog don't retry to send logs
// https://github.com/mperdeck/jsnlog.js/blob/v2.30.0/jsnlog.ts#L805-L818
const parentReadyStateFunction = xhr.onreadystatechange;
xhr.onreadystatechange = () => {
if (this.isInvalidResponse(xhr)) {
Object.defineProperty(xhr, 'readyState', { value: 4 });
Object.defineProperty(xhr, 'status', { value: 200 });
// Disable JSNLog too to not send periodically errors
this.instance.disableLogger();
}
parentReadyStateFunction();
};
// Headers to identify and authenticate logs
xhr.setRequestHeader('Authorization', 'Basic ' + btoa(`${finalUserId}%/%${sessionId}` + ':' + token));
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
// Additional headers for OpenVidu
xhr.setRequestHeader('OV-Final-User-Id', finalUserId);
xhr.setRequestHeader('OV-Session-Id', sessionId);
xhr.setRequestHeader('OV-Token', token);
};
// Creation of the appender.
this.instance.currentAppender = JL.createAjaxAppender(`appender-${finalUserId}-${sessionId}`);
this.instance.currentAppender.setOptions({
beforeSend: beforeSendCallback,
maxBatchSize: 1000,
batchSize: this.instance.MAX_JSNLOG_BATCH_LOG_MESSAGES,
batchTimeout: this.instance.MAX_MSECONDS_BATCH_MESSAGES
});
// Avoid circular dependencies
const logSerializer = (obj): string => {
const getCircularReplacer = () => {
const seen = new WeakSet();
return (key, value) => {
if (typeof value === 'object' && value != null) {
if (seen.has(value) || (globalThis.HTMLElement && value instanceof HTMLElement)) {
return;
}
seen.add(value);
}
return value;
};
};
// Cut long messages
let stringifyJson = JSON.stringify(obj, getCircularReplacer());
if (stringifyJson.length > this.instance.MAX_LENGTH_STRING_JSON) {
stringifyJson = `${stringifyJson.substring(0, this.instance.MAX_LENGTH_STRING_JSON)}...`;
}
if (globalThis['LOG_JSNLOG_RESULTS']) {
console.log(stringifyJson);
}
return stringifyJson;
};
// Initialize JL to send logs
JL.setOptions({
defaultAjaxUrl: openVidu.httpUri + this.instance.JSNLOG_URL,
serialize: logSerializer,
enabled: true
});
JL().setOptions({
appenders: [this.instance.currentAppender]
});
this.instance.isJSNLogSetup = true;
this.instance.loggingSessionId = sessionId;
this.instance.info('JSNLog configured.');
}
} catch (e) {
// Print error
console.error('Error configuring JSNLog: ');
console.error(e);
// Restore defaults values just in case any exception happen-
this.instance.disableLogger();
}
}
/**
* @hidden
*/
static getInstance(): OpenViduLogger {
if (!OpenViduLogger.instance) {
OpenViduLogger.instance = new OpenViduLogger();
}
return OpenViduLogger.instance;
}
private static isInvalidResponse(xhr: XMLHttpRequest) {
return xhr.status == 401 || xhr.status == 403 || xhr.status == 404 || xhr.status == 0;
}
private canConfigureJSNLog(openVidu: OpenVidu, logger: OpenViduLogger): boolean {
return openVidu.session.sessionId != logger.loggingSessionId;
}
private isOpenViduBrowserLogsDebugActive(openVidu: OpenVidu) {
return (
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug ||
openVidu.sendBrowserLogs === OpenViduLoggerConfiguration.debug_app
);
}
// Return console functions with jsnlog integration
private getConsoleWithJSNLog() {
return (function (openViduLogger: OpenViduLogger) {
return {
log: function (...args) {
openViduLogger.defaultConsoleLogger.log.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
if (openViduLogger.isJSNLogSetup) {
JL().info(arguments);
}
},
info: function (...args) {
openViduLogger.defaultConsoleLogger.info.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
if (openViduLogger.isJSNLogSetup) {
JL().info(arguments);
}
},
debug: function (...args) {
openViduLogger.defaultConsoleLogger.debug.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
},
warn: function (...args) {
openViduLogger.defaultConsoleLogger.warn.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
if (openViduLogger.isJSNLogSetup) {
JL().warn(arguments);
}
},
error: function (...args) {
openViduLogger.defaultConsoleLogger.error.apply(openViduLogger.defaultConsoleLogger.logger, arguments);
if (openViduLogger.isJSNLogSetup) {
JL().error(arguments);
}
}
};
})(this);
}
private replaceWindowConsole() {
globalThis.console = this.defaultConsoleLogger.logger;
globalThis.console.log = this.getConsoleWithJSNLog().log;
globalThis.console.info = this.getConsoleWithJSNLog().info;
globalThis.console.debug = this.getConsoleWithJSNLog().debug;
globalThis.console.warn = this.getConsoleWithJSNLog().warn;
globalThis.console.error = this.getConsoleWithJSNLog().error;
}
private disableLogger() {
JL.setOptions({ enabled: false });
this.isJSNLogSetup = false;
this.loggingSessionId = undefined;
this.currentAppender = undefined;
globalThis.console = this.defaultConsoleLogger.logger;
globalThis.console.log = this.defaultConsoleLogger.log;
globalThis.console.info = this.defaultConsoleLogger.info;
globalThis.console.debug = this.defaultConsoleLogger.debug;
globalThis.console.warn = this.defaultConsoleLogger.warn;
globalThis.console.error = this.defaultConsoleLogger.error;
}
/**
* @hidden
*/
log(...args: any[]) {
if (!this.isProdMode) {
this.defaultConsoleLogger.log.apply(this.defaultConsoleLogger.logger, arguments);
}
if (this.isJSNLogSetup) {
JL().info(arguments);
}
}
/**
* @hidden
*/
debug(...args: any[]) {
if (!this.isProdMode) {
this.defaultConsoleLogger.debug.apply(this.defaultConsoleLogger.logger, arguments);
}
}
/**
* @hidden
*/
info(...args: any[]) {
if (!this.isProdMode) {
this.defaultConsoleLogger.info.apply(this.defaultConsoleLogger.logger, arguments);
}
if (this.isJSNLogSetup) {
JL().info(arguments);
}
}
/**
* @hidden
*/
warn(...args: any[]) {
this.defaultConsoleLogger.warn.apply(this.defaultConsoleLogger.logger, arguments);
if (this.isJSNLogSetup) {
JL().warn(arguments);
}
}
/**
* @hidden
*/
error(...args: any[]) {
this.defaultConsoleLogger.error.apply(this.defaultConsoleLogger.logger, arguments);
if (this.isJSNLogSetup) {
JL().error(arguments);
}
}
/**
* @hidden
*/
flush() {
if (this.isJSNLogSetup && this.currentAppender != null) {
this.currentAppender.sendBatch();
}
}
enableProdMode() {
this.isProdMode = true;
}
}

View File

@ -1,5 +0,0 @@
export enum OpenViduLoggerConfiguration {
disabled = 'disabled',
debug = 'debug',
debug_app = 'debug_app'
}

View File

@ -1,233 +0,0 @@
// Last time updated on June 08, 2018
// Latest file can be found here: https://cdn.webrtc-experiment.com/getScreenId.js
// Muaz Khan - www.MuazKhan.com
// MIT License - www.WebRTC-Experiment.com/licence
// Documentation - https://github.com/muaz-khan/getScreenId.
// ______________
// getScreenId.js
/*
getScreenId(function (error, sourceId, screen_constraints) {
// error == null || 'permission-denied' || 'not-installed' || 'installed-disabled' || 'not-chrome'
// sourceId == null || 'string' || 'firefox'
if(microsoftEdge) {
navigator.getDisplayMedia(screen_constraints).then(onSuccess, onFailure);
}
else {
navigator.mediaDevices.getUserMedia(screen_constraints).then(onSuccess)catch(onFailure);
}
}, 'pass second parameter only if you want system audio');
*/
globalThis.getScreenId = function (firefoxString, callback, custom_parameter) {
if (navigator.userAgent.indexOf('Edge') !== -1 && (!!navigator.msSaveOrOpenBlob || !!navigator.msSaveBlob)) {
// microsoft edge => navigator.getDisplayMedia(screen_constraints).then(onSuccess, onFailure);
callback({
video: true
});
return;
}
// for Firefox:
// sourceId == 'firefox'
// screen_constraints = {...}
if (!!navigator.mozGetUserMedia) {
callback(null, 'firefox', {
video: {
mozMediaSource: firefoxString,
mediaSource: firefoxString
}
});
return;
}
globalThis.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeMediaSourceId) {
if (event.data.chromeMediaSourceId === 'PermissionDeniedError') {
callback('permission-denied');
} else {
callback(
null,
event.data.chromeMediaSourceId,
getScreenConstraints(null, event.data.chromeMediaSourceId, event.data.canRequestAudioTrack)
);
}
// this event listener is no more needed
globalThis.removeEventListener('message', onIFrameCallback);
}
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus, null, getScreenConstraints(event.data.chromeExtensionStatus));
// this event listener is no more needed
globalThis.removeEventListener('message', onIFrameCallback);
}
}
if (!custom_parameter) {
setTimeout(postGetSourceIdMessage, 100);
} else {
setTimeout(function () {
postGetSourceIdMessage(custom_parameter);
}, 100);
}
};
function getScreenConstraints(error, sourceId, canRequestAudioTrack) {
var screen_constraints = {
audio: false,
video: {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop',
maxWidth: globalThis.screen.width > 1920 ? globalThis.screen.width : 1920,
maxHeight: globalThis.screen.height > 1080 ? globalThis.screen.height : 1080
},
optional: []
}
};
if (!!canRequestAudioTrack) {
screen_constraints.audio = {
mandatory: {
chromeMediaSource: error ? 'screen' : 'desktop'
// echoCancellation: true
},
optional: []
};
}
if (sourceId) {
screen_constraints.video.mandatory.chromeMediaSourceId = sourceId;
if (screen_constraints.audio && screen_constraints.audio.mandatory) {
screen_constraints.audio.mandatory.chromeMediaSourceId = sourceId;
}
}
return screen_constraints;
}
function postGetSourceIdMessage(custom_parameter) {
if (!iframe) {
loadIFrame(function () {
postGetSourceIdMessage(custom_parameter);
});
return;
}
if (!iframe.isLoaded) {
setTimeout(function () {
postGetSourceIdMessage(custom_parameter);
}, 100);
return;
}
if (!custom_parameter) {
iframe.contentWindow.postMessage(
{
captureSourceId: true
},
'*'
);
} else if (!!custom_parameter.forEach) {
iframe.contentWindow.postMessage(
{
captureCustomSourceId: custom_parameter
},
'*'
);
} else {
iframe.contentWindow.postMessage(
{
captureSourceIdWithAudio: true
},
'*'
);
}
}
var iframe;
// this function is used in RTCMultiConnection v3
globalThis.getScreenConstraints = function (callback) {
loadIFrame(function () {
getScreenId(function (error, sourceId, screen_constraints) {
if (!screen_constraints) {
screen_constraints = {
video: true
};
}
callback(error, screen_constraints.video);
});
});
};
function loadIFrame(loadCallback) {
if (iframe) {
loadCallback();
return;
}
iframe = document.createElement('iframe');
iframe.onload = function () {
iframe.isLoaded = true;
loadCallback();
};
iframe.src = 'https://openvidu.github.io/openvidu-screen-sharing-chrome-extension/';
iframe.style.display = 'none';
(document.body || document.documentElement).appendChild(iframe);
}
globalThis.getChromeExtensionStatus = function (callback) {
// for Firefox:
if (!!navigator.mozGetUserMedia) {
callback('installed-enabled');
return;
}
globalThis.addEventListener('message', onIFrameCallback);
function onIFrameCallback(event) {
if (!event.data) return;
if (event.data.chromeExtensionStatus) {
callback(event.data.chromeExtensionStatus);
// this event listener is no more needed
globalThis.removeEventListener('message', onIFrameCallback);
}
}
setTimeout(postGetChromeExtensionStatusMessage, 100);
};
function postGetChromeExtensionStatusMessage() {
if (!iframe) {
loadIFrame(postGetChromeExtensionStatusMessage);
return;
}
if (!iframe.isLoaded) {
setTimeout(postGetChromeExtensionStatusMessage, 100);
return;
}
iframe.contentWindow.postMessage(
{
getChromeExtensionStatus: true
},
'*'
);
}
exports.getScreenId = globalThis.getScreenId;

View File

@ -1,162 +0,0 @@
// global variables
var chromeMediaSource = 'screen';
var sourceId;
var screenCallback;
if (typeof window !== 'undefined' && typeof navigator !== 'undefined' && typeof navigator.userAgent !== 'undefined') {
var isFirefox = typeof window.InstallTrigger !== 'undefined';
var isOpera = !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0;
var isChrome = !!window.chrome && !isOpera;
window.addEventListener('message', function (event) {
if (event.origin != window.location.origin) {
return;
}
onMessageCallback(event.data);
});
}
// and the function that handles received messages
function onMessageCallback(data) {
// "cancel" button is clicked
if (data == 'PermissionDeniedError') {
if (screenCallback) return screenCallback('PermissionDeniedError');
else throw new Error('PermissionDeniedError');
}
// extension notified his presence
if (data == 'rtcmulticonnection-extension-loaded') {
chromeMediaSource = 'desktop';
}
// extension shared temp sourceId
if (data.sourceId && screenCallback) {
screenCallback((sourceId = data.sourceId), data.canRequestAudioTrack === true);
}
}
// this method can be used to check if chrome extension is installed & enabled.
function isChromeExtensionAvailable(callback) {
if (!callback) return;
if (chromeMediaSource == 'desktop') return callback(true);
// ask extension if it is available
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback(false);
} else callback(true);
}, 2000);
}
// this function can be used to get "source-id" from the extension
function getSourceId(callback) {
if (!callback) throw '"callback" parameter is mandatory.';
if (sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage('get-sourceId', '*');
}
// this function can be used to get "source-id" from the extension
function getCustomSourceId(arr, callback) {
if (!arr || !arr.forEach) throw '"arr" parameter is mandatory and it must be an array.';
if (!callback) throw '"callback" parameter is mandatory.';
if (sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage(
{
'get-custom-sourceId': arr
},
'*'
);
}
// this function can be used to get "source-id" from the extension
function getSourceIdWithAudio(callback) {
if (!callback) throw '"callback" parameter is mandatory.';
if (sourceId) return callback(sourceId);
screenCallback = callback;
window.postMessage('audio-plus-tab', '*');
}
function getChromeExtensionStatus(extensionid, callback) {
if (isFirefox) return callback('not-chrome');
if (arguments.length != 2) {
callback = extensionid;
extensionid = 'lfcgfepafnobdloecchnfaclibenjold'; // default extension-id
}
var image = document.createElement('img');
image.src = 'chrome-extension://' + extensionid + '/icon.png';
image.onload = function () {
chromeMediaSource = 'screen';
window.postMessage('are-you-there', '*');
setTimeout(function () {
if (chromeMediaSource == 'screen') {
callback('installed-disabled');
} else callback('installed-enabled');
}, 2000);
};
image.onerror = function () {
callback('not-installed');
};
}
function getScreenConstraintsWithAudio(callback) {
getScreenConstraints(callback, true);
}
// this function explains how to use above methods/objects
function getScreenConstraints(callback, captureSourceIdWithAudio) {
sourceId = '';
var firefoxScreenConstraints = {
mozMediaSource: 'window',
mediaSource: 'window'
};
if (isFirefox) return callback(null, firefoxScreenConstraints);
// this statement defines getUserMedia constraints
// that will be used to capture content of screen
var screen_constraints = {
mandatory: {
chromeMediaSource: chromeMediaSource,
maxWidth: screen.width > 1920 ? screen.width : 1920,
maxHeight: screen.height > 1080 ? screen.height : 1080
},
optional: []
};
// this statement verifies chrome extension availability
// if installed and available then it will invoke extension API
// otherwise it will fallback to command-line based screen capturing API
if (chromeMediaSource == 'desktop' && !sourceId) {
if (captureSourceIdWithAudio) {
getSourceIdWithAudio(function (sourceId, canRequestAudioTrack) {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
if (canRequestAudioTrack) {
screen_constraints.canRequestAudioTrack = true;
}
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
} else {
getSourceId(function (sourceId) {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
callback(sourceId == 'PermissionDeniedError' ? sourceId : null, screen_constraints);
});
}
return;
}
// this statement sets gets 'sourceId" and sets "chromeMediaSourceId"
if (chromeMediaSource == 'desktop') {
screen_constraints.mandatory.chromeMediaSourceId = sourceId;
}
// now invoking native getUserMedia API
callback(null, screen_constraints);
}
exports.getScreenConstraints = getScreenConstraints;
exports.getScreenConstraintsWithAudio = getScreenConstraintsWithAudio;
exports.isChromeExtensionAvailable = isChromeExtensionAvailable;
exports.getChromeExtensionStatus = getChromeExtensionStatus;
exports.getSourceId = getSourceId;

View File

@ -1,233 +0,0 @@
import platform = require('platform');
export class PlatformUtils {
protected static instance: PlatformUtils;
constructor() {}
static getInstance(): PlatformUtils {
if (!this.instance) {
this.instance = new PlatformUtils();
}
return PlatformUtils.instance;
}
public isChromeBrowser(): boolean {
return platform.name === 'Chrome';
}
/**
* @hidden
*/
public isSafariBrowser(): boolean {
return platform.name === 'Safari';
}
/**
* @hidden
*/
public isChromeMobileBrowser(): boolean {
return platform.name === 'Chrome Mobile';
}
/**
* @hidden
*/
public isFirefoxBrowser(): boolean {
return platform.name === 'Firefox';
}
/**
* @hidden
*/
public isFirefoxMobileBrowser(): boolean {
return platform.name === 'Firefox Mobile' || platform.name === 'Firefox for iOS';
}
/**
* @hidden
*/
public isOperaBrowser(): boolean {
return platform.name === 'Opera';
}
/**
* @hidden
*/
public isOperaMobileBrowser(): boolean {
return platform.name === 'Opera Mobile';
}
/**
* @hidden
*/
public isEdgeBrowser(): boolean {
const version = platform?.version ? parseFloat(platform.version) : -1;
return platform.name === 'Microsoft Edge' && version >= 80;
}
/**
* @hidden
*/
public isEdgeMobileBrowser(): boolean {
const version = platform?.version ? parseFloat(platform.version) : -1;
return platform.name === 'Microsoft Edge' && (platform.os?.family === 'Android' || platform.os?.family === 'iOS') && version > 45;
}
/**
* @hidden
*/
public isAndroidBrowser(): boolean {
return platform.name === 'Android Browser';
}
/**
* @hidden
*/
public isElectron(): boolean {
return platform.name === 'Electron';
}
/**
* @hidden
*/
public isNodeJs(): boolean {
return platform.name === 'Node.js';
}
/**
* @hidden
*/
public isSamsungBrowser(): boolean {
return platform.name === 'Samsung Internet Mobile' || platform.name === 'Samsung Internet';
}
// TODO: This method exists to overcome bug https://github.com/bestiejs/platform.js/issues/184
/**
* @hidden
*/
public isMotorolaEdgeDevice(): boolean {
return platform.product?.toLowerCase().includes('motorola edge') || false;
}
/**
* @hidden
*/
public isIPhoneOrIPad(): boolean {
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
const isTouchable = 'ontouchend' in document;
const isIPad = /\b(\w*Macintosh\w*)\b/.test(userAgent) && isTouchable;
const isIPhone = /\b(\w*iPhone\w*)\b/.test(userAgent) && /\b(\w*Mobile\w*)\b/.test(userAgent) && isTouchable;
return isIPad || isIPhone;
}
/**
* @hidden
*/
public isIOSWithSafari(): boolean {
const userAgent = !!platform.ua ? platform.ua : navigator.userAgent;
return (
this.isIPhoneOrIPad() &&
/\b(\w*Apple\w*)\b/.test(navigator.vendor) &&
/\b(\w*Safari\w*)\b/.test(userAgent) &&
!/\b(\w*CriOS\w*)\b/.test(userAgent) &&
!/\b(\w*FxiOS\w*)\b/.test(userAgent)
);
}
/**
* @hidden
*/
public isIonicIos(): boolean {
return this.isIPhoneOrIPad() && platform.ua!!.indexOf('Safari') === -1;
}
/**
* @hidden
*/
public isIonicAndroid(): boolean {
return platform.os!!.family === 'Android' && platform.name == 'Android Browser';
}
/**
* @hidden
*/
public isMobileDevice(): boolean {
return platform.os!!.family === 'iOS' || platform.os!!.family === 'Android';
}
/**
* @hidden
*/
public isReactNative(): boolean {
return false;
}
/**
* @hidden
*/
public isChromium(): boolean {
return (
this.isChromeBrowser() ||
this.isChromeMobileBrowser() ||
this.isOperaBrowser() ||
this.isOperaMobileBrowser() ||
this.isEdgeBrowser() ||
this.isEdgeMobileBrowser() ||
this.isSamsungBrowser() ||
this.isIonicAndroid() ||
this.isIonicIos() ||
this.isElectron() ||
// TODO: remove when possible
this.isMotorolaEdgeDevice()
);
}
/**
* @hidden
*/
public canScreenShare(): boolean {
const version = platform?.version ? parseFloat(platform.version) : -1;
// Reject mobile devices
if (this.isMobileDevice()) {
return false;
}
return (
this.isChromeBrowser() ||
this.isFirefoxBrowser() ||
this.isOperaBrowser() ||
this.isElectron() ||
this.isEdgeBrowser() ||
(this.isSafariBrowser() && version >= 13)
);
}
/**
* @hidden
*/
public getName(): string {
return platform.name || '';
}
/**
* @hidden
*/
public getVersion(): string {
return platform.version || '';
}
/**
* @hidden
*/
public getFamily(): string {
return platform.os!!.family || '';
}
/**
* @hidden
*/
public getDescription(): string {
return platform.description || '';
}
}

View File

@ -1,600 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import freeice = require('freeice');
import { v4 as uuidv4 } from 'uuid';
import { TypeOfVideo } from '../Enums/TypeOfVideo';
import { ExceptionEventName } from '../Events/ExceptionEvent';
import { OpenViduLogger } from '../Logger/OpenViduLogger';
import { PlatformUtils } from '../Utils/Platform';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
let platform: PlatformUtils;
export interface WebRtcPeerConfiguration {
mediaConstraints: {
audio: boolean;
video: boolean;
};
simulcast: boolean;
mediaServer: string;
onIceCandidate: (event: RTCIceCandidate) => void;
onIceConnectionStateException: (exceptionName: ExceptionEventName, message: string, data?: any) => void;
iceServers?: RTCIceServer[];
rtcConfiguration?: RTCConfiguration;
mediaStream?: MediaStream | null;
mode?: 'sendonly' | 'recvonly' | 'sendrecv';
id?: string;
typeOfVideo: TypeOfVideo | undefined;
}
export class WebRtcPeer {
pc: RTCPeerConnection;
remoteCandidatesQueue: RTCIceCandidate[] = [];
localCandidatesQueue: RTCIceCandidate[] = [];
// Same as WebRtcPeerConfiguration but without optional fields.
protected configuration: Required<WebRtcPeerConfiguration>;
private iceCandidateList: RTCIceCandidate[] = [];
constructor(configuration: WebRtcPeerConfiguration) {
platform = PlatformUtils.getInstance();
this.configuration = {
...configuration,
iceServers: !!configuration.iceServers && configuration.iceServers.length > 0 ? configuration.iceServers : freeice(),
rtcConfiguration: configuration.rtcConfiguration !== undefined ? configuration.rtcConfiguration : {},
mediaStream: configuration.mediaStream !== undefined ? configuration.mediaStream : null,
mode: !!configuration.mode ? configuration.mode : 'sendrecv',
id: !!configuration.id ? configuration.id : this.generateUniqueId()
};
// prettier-ignore
logger.debug(`[WebRtcPeer] configuration:\n${JSON.stringify(this.configuration, null, 2)}`);
let rtcConfiguration: RTCConfiguration = this.configuration.rtcConfiguration
? this.configuration.rtcConfiguration
: { iceServers: this.configuration.iceServers };
if (!rtcConfiguration.iceServers && this.configuration.iceServers) {
rtcConfiguration.iceServers = this.configuration.iceServers;
}
this.pc = new RTCPeerConnection(rtcConfiguration);
this.pc.addEventListener('icecandidate', (event: RTCPeerConnectionIceEvent) => {
if (event.candidate !== null) {
// `RTCPeerConnectionIceEvent.candidate` is supposed to be an RTCIceCandidate:
// https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnectioniceevent-candidate
//
// But in practice, it is actually an RTCIceCandidateInit that can be used to
// obtain a proper candidate, using the RTCIceCandidate constructor:
// https://w3c.github.io/webrtc-pc/#dom-rtcicecandidate-constructor
const candidateInit: RTCIceCandidateInit = event.candidate as RTCIceCandidateInit;
const iceCandidate = new RTCIceCandidate(candidateInit);
this.configuration.onIceCandidate(iceCandidate);
if (iceCandidate.candidate !== '') {
this.localCandidatesQueue.push(iceCandidate);
}
}
});
this.pc.addEventListener('signalingstatechange', async () => {
if (this.pc.signalingState === 'stable') {
// SDP Offer/Answer finished. Add stored remote candidates.
while (this.iceCandidateList.length > 0) {
let candidate = this.iceCandidateList.shift();
try {
await this.pc.addIceCandidate(<RTCIceCandidate>candidate);
} catch (error) {
logger.error('Error when calling RTCPeerConnection#addIceCandidate for RTCPeerConnection ' + this.getId(), error);
}
}
}
});
}
getId(): string {
return this.configuration.id;
}
/**
* This method frees the resources used by WebRtcPeer
*/
dispose() {
logger.debug('Disposing WebRtcPeer');
if (this.pc) {
if (this.pc.signalingState === 'closed') {
return;
}
this.pc.close();
this.remoteCandidatesQueue = [];
this.localCandidatesQueue = [];
}
}
// DEPRECATED LEGACY METHOD: Old WebRTC versions don't implement
// Transceivers, and instead depend on the deprecated
// "offerToReceiveAudio" and "offerToReceiveVideo".
private createOfferLegacy(): Promise<RTCSessionDescriptionInit> {
if (!!this.configuration.mediaStream) {
this.deprecatedPeerConnectionTrackApi();
}
const hasAudio = this.configuration.mediaConstraints.audio;
const hasVideo = this.configuration.mediaConstraints.video;
const options: RTCOfferOptions = {
offerToReceiveAudio: this.configuration.mode !== 'sendonly' && hasAudio,
offerToReceiveVideo: this.configuration.mode !== 'sendonly' && hasVideo
};
logger.debug('[createOfferLegacy] RTCPeerConnection.createOffer() options:', JSON.stringify(options));
return this.pc.createOffer(options);
}
/**
* Creates an SDP offer from the local RTCPeerConnection to send to the other peer.
* Only if the negotiation was initiated by this peer.
*/
async createOffer(): Promise<RTCSessionDescriptionInit> {
// TODO: Delete this conditional when all supported browsers are
// modern enough to implement the Transceiver methods.
if (!('addTransceiver' in this.pc)) {
logger.warn(
'[createOffer] Method RTCPeerConnection.addTransceiver() is NOT available; using LEGACY offerToReceive{Audio,Video}'
);
return this.createOfferLegacy();
} else {
logger.debug('[createOffer] Method RTCPeerConnection.addTransceiver() is available; using it');
}
// Spec doc: https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-addtransceiver
if (this.configuration.mode !== 'recvonly') {
// To send media, assume that all desired media tracks have been
// already added by higher level code to our MediaStream.
if (!this.configuration.mediaStream) {
throw new Error(
`[WebRtcPeer.createOffer] Direction is '${this.configuration.mode}', but no stream was configured to be sent`
);
}
for (const track of this.configuration.mediaStream.getTracks()) {
const tcInit: RTCRtpTransceiverInit = {
direction: this.configuration.mode,
streams: [this.configuration.mediaStream]
};
if (track.kind === 'video' && this.configuration.simulcast) {
// Check if the requested size is enough to ask for 3 layers.
const trackSettings = track.getSettings();
const trackConsts = track.getConstraints();
const trackWidth: number =
trackSettings.width ?? (trackConsts.width as ConstrainULongRange).ideal ?? (trackConsts.width as number) ?? 0;
const trackHeight: number =
trackSettings.height ?? (trackConsts.height as ConstrainULongRange).ideal ?? (trackConsts.height as number) ?? 0;
logger.info(`[createOffer] Video track dimensions: ${trackWidth}x${trackHeight}`);
const trackPixels = trackWidth * trackHeight;
let maxLayers = 0;
if (trackPixels >= 960 * 540) {
maxLayers = 3;
} else if (trackPixels >= 480 * 270) {
maxLayers = 2;
} else {
maxLayers = 1;
}
tcInit.sendEncodings = [];
for (let l = 0; l < maxLayers; l++) {
const layerDiv = 2 ** (maxLayers - l - 1);
const encoding: RTCRtpEncodingParameters = {
rid: 'rdiv' + layerDiv.toString(),
// @ts-ignore -- Property missing from DOM types.
scalabilityMode: 'L1T1'
};
if (['detail', 'text'].includes(track.contentHint)) {
// Prioritize best resolution, for maximum picture detail.
encoding.scaleResolutionDownBy = 1.0;
// @ts-ignore -- Property missing from DOM types.
encoding.maxFramerate = Math.floor(30 / layerDiv);
} else {
encoding.scaleResolutionDownBy = layerDiv;
}
tcInit.sendEncodings.push(encoding);
}
}
const tc = this.pc.addTransceiver(track, tcInit);
if (track.kind === 'video') {
let sendParams = tc.sender.getParameters();
let needSetParams = false;
if (!sendParams.degradationPreference?.length) {
// degradationPreference for video: "balanced", "maintain-framerate", "maintain-resolution".
// https://www.w3.org/TR/2018/CR-webrtc-20180927/#dom-rtcdegradationpreference
if (['detail', 'text'].includes(track.contentHint)) {
sendParams.degradationPreference = 'maintain-resolution';
} else {
sendParams.degradationPreference = 'balanced';
}
logger.info(`[createOffer] Video sender Degradation Preference set: ${sendParams.degradationPreference}`);
// FIXME: Firefox implements degradationPreference on each individual encoding!
// (set it on every element of the sendParams.encodings array)
needSetParams = true;
}
// FIXME: Check that the simulcast encodings were applied.
// Firefox doesn't implement `RTCRtpTransceiverInit.sendEncodings`
// so the only way to enable simulcast is with `RTCRtpSender.setParameters()`.
//
// This next block can be deleted when Firefox fixes bug #1396918:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1396918
//
// NOTE: This is done in a way that is compatible with all browsers, to save on
// browser-conditional code. The idea comes from WebRTC Adapter.js:
// * https://github.com/webrtcHacks/adapter/issues/998
// * https://github.com/webrtcHacks/adapter/blob/v7.7.0/src/js/firefox/firefox_shim.js#L231-L255
if (this.configuration.simulcast) {
if (sendParams.encodings?.length !== tcInit.sendEncodings!.length) {
sendParams.encodings = tcInit.sendEncodings!;
needSetParams = true;
}
}
if (needSetParams) {
logger.debug(`[createOffer] Setting new RTCRtpSendParameters to video sender`);
try {
await tc.sender.setParameters(sendParams);
} catch (error) {
let message = `[WebRtcPeer.createOffer] Cannot set RTCRtpSendParameters to video sender`;
if (error instanceof Error) {
message += `: ${error.message}`;
}
throw new Error(message);
}
}
}
// DEBUG: Uncomment for details.
// if (track.kind === "video" && this.configuration.simulcast) {
// // Print browser capabilities.
// // prettier-ignore
// logger.debug(`[createOffer] Transceiver send capabilities (static):\n${JSON.stringify(RTCRtpSender.getCapabilities?.("video"), null, 2)}`);
// // prettier-ignore
// logger.debug(`[createOffer] Transceiver recv capabilities (static):\n${JSON.stringify(RTCRtpReceiver.getCapabilities?.("video"), null, 2)}`);
// // Print requested Transceiver encodings and parameters.
// // prettier-ignore
// logger.debug(`[createOffer] Transceiver send encodings (requested):\n${JSON.stringify(tcInit.sendEncodings, null, 2)}`);
// // prettier-ignore
// logger.debug(`[createOffer] Transceiver send parameters (accepted):\n${JSON.stringify(tc.sender.getParameters(), null, 2)}`);
// }
}
} else {
// To just receive media, create new recvonly transceivers.
for (const kind of ['audio', 'video']) {
// Check if the media kind should be used.
if (!this.configuration.mediaConstraints[kind]) {
continue;
}
this.configuration.mediaStream = new MediaStream();
this.pc.addTransceiver(kind, {
direction: this.configuration.mode,
streams: [this.configuration.mediaStream]
});
}
}
let sdpOffer: RTCSessionDescriptionInit;
try {
sdpOffer = await this.pc.createOffer();
} catch (error) {
let message = `[WebRtcPeer.createOffer] Browser failed creating an SDP Offer`;
if (error instanceof Error) {
message += `: ${error.message}`;
}
throw new Error(message);
}
return sdpOffer;
}
deprecatedPeerConnectionTrackApi() {
for (const track of this.configuration.mediaStream!.getTracks()) {
this.pc.addTrack(track, this.configuration.mediaStream!);
}
}
/**
* Creates an SDP answer from the local RTCPeerConnection to send to the other peer
* Only if the negotiation was initiated by the other peer
*/
createAnswer(): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => {
// TODO: Delete this conditional when all supported browsers are
// modern enough to implement the Transceiver methods.
if ('getTransceivers' in this.pc) {
logger.debug('[createAnswer] Method RTCPeerConnection.getTransceivers() is available; using it');
// Ensure that the PeerConnection already contains one Transceiver
// for each kind of media.
// The Transceivers should have been already created internally by
// the PC itself, when `pc.setRemoteDescription(sdpOffer)` was called.
for (const kind of ['audio', 'video']) {
// Check if the media kind should be used.
if (!this.configuration.mediaConstraints[kind]) {
continue;
}
let tc = this.pc.getTransceivers().find((tc) => tc.receiver.track.kind === kind);
if (tc) {
// Enforce our desired direction.
tc.direction = this.configuration.mode;
} else {
return reject(new Error(`${kind} requested, but no transceiver was created from remote description`));
}
}
this.pc
.createAnswer()
.then((sdpAnswer) => resolve(sdpAnswer))
.catch((error) => reject(error));
} else {
// TODO: Delete else branch when all supported browsers are
// modern enough to implement the Transceiver methods
let offerAudio,
offerVideo = true;
if (!!this.configuration.mediaConstraints) {
offerAudio =
typeof this.configuration.mediaConstraints.audio === 'boolean' ? this.configuration.mediaConstraints.audio : true;
offerVideo =
typeof this.configuration.mediaConstraints.video === 'boolean' ? this.configuration.mediaConstraints.video : true;
const constraints: RTCOfferOptions = {
offerToReceiveAudio: offerAudio,
offerToReceiveVideo: offerVideo
};
(this.pc as RTCPeerConnection).createAnswer(constraints)
.then((sdpAnswer) => resolve(sdpAnswer))
.catch((error) => reject(error));
}
}
// else, there is nothing to do; the legacy createAnswer() options do
// not offer any control over which tracks are included in the answer.
});
}
/**
* This peer initiated negotiation. Step 1/4 of SDP offer-answer protocol
*/
processLocalOffer(offer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
this.pc
.setLocalDescription(offer)
.then(() => {
const localDescription = this.pc.localDescription;
if (!!localDescription) {
logger.debug('Local description set', localDescription.sdp);
return resolve();
} else {
return reject('Local description is not defined');
}
})
.catch((error) => reject(error));
});
}
/**
* Other peer initiated negotiation. Step 2/4 of SDP offer-answer protocol
*/
processRemoteOffer(sdpOffer: string): Promise<void> {
return new Promise((resolve, reject) => {
const offer: RTCSessionDescriptionInit = {
type: 'offer',
sdp: sdpOffer
};
logger.debug('SDP offer received, setting remote description', offer);
if (this.pc.signalingState === 'closed') {
return reject('RTCPeerConnection is closed when trying to set remote description');
}
this.setRemoteDescription(offer)
.then(() => resolve())
.catch((error) => reject(error));
});
}
/**
* Other peer initiated negotiation. Step 3/4 of SDP offer-answer protocol
*/
processLocalAnswer(answer: RTCSessionDescriptionInit): Promise<void> {
return new Promise((resolve, reject) => {
logger.debug('SDP answer created, setting local description');
if (this.pc.signalingState === 'closed') {
return reject('RTCPeerConnection is closed when trying to set local description');
}
this.pc
.setLocalDescription(answer)
.then(() => resolve())
.catch((error) => reject(error));
});
}
/**
* This peer initiated negotiation. Step 4/4 of SDP offer-answer protocol
*/
processRemoteAnswer(sdpAnswer: string): Promise<void> {
return new Promise((resolve, reject) => {
const answer: RTCSessionDescriptionInit = {
type: 'answer',
sdp: sdpAnswer
};
logger.debug('SDP answer received, setting remote description');
if (this.pc.signalingState === 'closed') {
return reject('RTCPeerConnection is closed when trying to set remote description');
}
this.setRemoteDescription(answer)
.then(() => {
// DEBUG: Uncomment for details.
// {
// const tc = this.pc.getTransceivers().find((tc) => tc.sender.track?.kind === "video");
// // prettier-ignore
// logger.debug(`[processRemoteAnswer] Transceiver send parameters (effective):\n${JSON.stringify(tc?.sender.getParameters(), null, 2)}`);
// }
resolve();
})
.catch((error) => reject(error));
});
}
/**
* @hidden
*/
async setRemoteDescription(sdp: RTCSessionDescriptionInit): Promise<void> {
return this.pc.setRemoteDescription(sdp);
}
/**
* Callback function invoked when an ICE candidate is received
*/
addIceCandidate(iceCandidate: RTCIceCandidate): Promise<void> {
return new Promise((resolve, reject) => {
logger.debug('Remote ICE candidate received', iceCandidate);
this.remoteCandidatesQueue.push(iceCandidate);
switch (this.pc.signalingState) {
case 'closed':
reject(new Error('PeerConnection object is closed'));
break;
case 'stable':
if (!!this.pc.remoteDescription) {
this.pc
.addIceCandidate(iceCandidate)
.then(() => resolve())
.catch((error) => reject(error));
} else {
this.iceCandidateList.push(iceCandidate);
resolve();
}
break;
default:
this.iceCandidateList.push(iceCandidate);
resolve();
}
});
}
addIceConnectionStateChangeListener(otherId: string) {
this.pc.addEventListener('iceconnectionstatechange', () => {
const iceConnectionState: RTCIceConnectionState = this.pc.iceConnectionState;
switch (iceConnectionState) {
case 'disconnected':
// Possible network disconnection
const msg1 =
'IceConnectionState of RTCPeerConnection ' +
this.configuration.id +
' (' +
otherId +
') change to "disconnected". Possible network disconnection';
logger.warn(msg1);
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_DISCONNECTED, msg1);
break;
case 'failed':
const msg2 = 'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') to "failed"';
logger.error(msg2);
this.configuration.onIceConnectionStateException(ExceptionEventName.ICE_CONNECTION_FAILED, msg2);
break;
case 'closed':
logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "closed"'
);
break;
case 'new':
logger.log('IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "new"');
break;
case 'checking':
logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "checking"'
);
break;
case 'connected':
logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "connected"'
);
break;
case 'completed':
logger.log(
'IceConnectionState of RTCPeerConnection ' + this.configuration.id + ' (' + otherId + ') change to "completed"'
);
break;
}
});
}
/**
* @hidden
*/
generateUniqueId(): string {
return uuidv4();
}
}
export class WebRtcPeerRecvonly extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'recvonly';
super(configuration);
}
}
export class WebRtcPeerSendonly extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'sendonly';
super(configuration);
}
}
export class WebRtcPeerSendrecv extends WebRtcPeer {
constructor(configuration: WebRtcPeerConfiguration) {
configuration.mode = 'sendrecv';
super(configuration);
}
}

View File

@ -1,471 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
// tslint:disable:no-string-literal
import { Stream } from '../../OpenVidu/Stream';
import { OpenViduLogger } from '../Logger/OpenViduLogger';
import { PlatformUtils } from '../Utils/Platform';
/**
* @hidden
*/
const logger: OpenViduLogger = OpenViduLogger.getInstance();
/**
* @hidden
*/
let platform: PlatformUtils;
interface WebrtcStatsConfig {
interval: number;
httpEndpoint: string;
}
interface JSONStatsResponse {
'@timestamp': string;
participant_id: string;
session_id: string;
platform: string;
platform_description: string;
stream: string;
webrtc_stats: IWebrtcStats;
}
/**
* Common WebRtcSTats for latest Chromium and Firefox versions
*/
interface IWebrtcStats {
inbound?: {
audio:
| {
bytesReceived: number;
packetsReceived: number;
packetsLost: number;
jitter: number;
}
| {};
video:
| {
bytesReceived: number;
packetsReceived: number;
packetsLost: number;
jitter?: number; // Firefox
jitterBufferDelay?: number; // Chrome
framesDecoded: number;
firCount: number;
nackCount: number;
pliCount: number;
frameHeight?: number; // Chrome
frameWidth?: number; // Chrome
framesDropped?: number; // Chrome
framesReceived?: number; // Chrome
}
| {};
};
outbound?: {
audio:
| {
bytesSent: number;
packetsSent: number;
}
| {};
video:
| {
bytesSent: number;
packetsSent: number;
firCount: number;
framesEncoded: number;
nackCount: number;
pliCount: number;
qpSum: number;
frameHeight?: number; // Chrome
frameWidth?: number; // Chrome
framesSent?: number; // Chrome
}
| {};
};
candidatepair?: {
currentRoundTripTime?: number; // Chrome
availableOutgoingBitrate?: number; //Chrome
// availableIncomingBitrate?: number // No support for any browsers (https://developer.mozilla.org/en-US/docs/Web/API/RTCIceCandidatePairStats/availableIncomingBitrate)
};
}
export class WebRtcStats {
private readonly STATS_ITEM_NAME = 'webrtc-stats-config';
private webRtcStatsEnabled = false;
private webRtcStatsIntervalId: NodeJS.Timer;
private statsInterval = 1;
private POST_URL: string;
constructor(private stream: Stream) {
platform = PlatformUtils.getInstance();
}
public isEnabled(): boolean {
return this.webRtcStatsEnabled;
}
public initWebRtcStats(): void {
let webrtcObj;
// When cross-site (aka third-party) cookies are blocked by the browser,
// accessing localStorage in a third-party iframe throws a DOMException.
try {
webrtcObj = localStorage.getItem(this.STATS_ITEM_NAME);
}
catch(e){}
if (!!webrtcObj) {
this.webRtcStatsEnabled = true;
const webrtcStatsConfig: WebrtcStatsConfig = JSON.parse(webrtcObj);
// webrtc object found in local storage
logger.warn(
'WebRtc stats enabled for stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
);
logger.warn('localStorage item: ' + JSON.stringify(webrtcStatsConfig));
this.POST_URL = webrtcStatsConfig.httpEndpoint;
this.statsInterval = webrtcStatsConfig.interval; // Interval in seconds
this.webRtcStatsIntervalId = setInterval(async () => {
await this.sendStatsToHttpEndpoint();
}, this.statsInterval * 1000);
} else {
logger.debug('WebRtc stats not enabled');
}
}
// {
// "localCandidate": {
// "id": "RTCIceCandidate_/r4P1y2Q",
// "timestamp": 1616080155617,
// "type": "local-candidate",
// "transportId": "RTCTransport_0_1",
// "isRemote": false,
// "networkType": "wifi",
// "ip": "123.45.67.89",
// "port": 63340,
// "protocol": "udp",
// "candidateType": "srflx",
// "priority": 1686052607,
// "deleted": false,
// "raw": [
// "candidate:3345412921 1 udp 1686052607 123.45.67.89 63340 typ srflx raddr 192.168.1.31 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10",
// "candidate:58094482 1 udp 41885695 98.76.54.32 44431 typ relay raddr 123.45.67.89 rport 63340 generation 0 ufrag 0ZtT network-id 1 network-cost 10"
// ]
// },
// "remoteCandidate": {
// "id": "RTCIceCandidate_1YO18gph",
// "timestamp": 1616080155617,
// "type": "remote-candidate",
// "transportId": "RTCTransport_0_1",
// "isRemote": true,
// "ip": "12.34.56.78",
// "port": 64989,
// "protocol": "udp",
// "candidateType": "srflx",
// "priority": 1679819263,
// "deleted": false,
// "raw": [
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989",
// "candidate:16 1 UDP 1679819263 12.34.56.78 64989 typ srflx raddr 172.19.0.1 rport 64989"
// ]
// }
// }
// Have been tested in:
// - Linux Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.218
// - Firefox 86
// - Microsoft Edge 91.0.825.0
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Windows Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.232
// - Firefox 86.0.1
// - Microsoft Edge 89.0.774.54
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - MacOS Desktop:
// - Chrome 89.0.4389.90
// - Firefox 87.0
// - Opera 75.0.3969.93
// - Microsoft Edge 89.0.774.57
// - Safari 14.0 (14610.1.28.1.9)
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Android:
// - Chrome Mobile 89.0.4389.90
// - Opera 62.3.3146.57763
// - Firefox Mobile 86.6.1
// - Microsoft Edge Mobile 46.02.4.5147
// - Ionic 5
// - React Native 0.64
// - iOS:
// - Safari Mobile
// - ¿Ionic?
// - ¿React Native?
public getSelectedIceCandidateInfo(): Promise<any> {
return new Promise(async (resolve, reject) => {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
let transportStat;
const candidatePairs: Map<string, any> = new Map();
const localCandidates: Map<string, any> = new Map();
const remoteCandidates: Map<string, any> = new Map();
statsReport.forEach((stat: any) => {
if (stat.type === 'transport' && (platform.isChromium() || platform.isSafariBrowser() || platform.isReactNative())) {
transportStat = stat;
}
switch (stat.type) {
case 'candidate-pair':
candidatePairs.set(stat.id, stat);
break;
case 'local-candidate':
localCandidates.set(stat.id, stat);
break;
case 'remote-candidate':
remoteCandidates.set(stat.id, stat);
break;
}
});
let selectedCandidatePair;
if (transportStat != null) {
const selectedCandidatePairId = transportStat.selectedCandidatePairId;
selectedCandidatePair = candidatePairs.get(selectedCandidatePairId);
} else {
// This is basically Firefox
const length = candidatePairs.size;
const iterator = candidatePairs.values();
for (let i = 0; i < length; i++) {
const candidatePair = iterator.next().value;
if (candidatePair['selected']) {
selectedCandidatePair = candidatePair;
break;
}
}
}
const localCandidateId = selectedCandidatePair.localCandidateId;
const remoteCandidateId = selectedCandidatePair.remoteCandidateId;
let finalLocalCandidate = localCandidates.get(localCandidateId);
if (!!finalLocalCandidate) {
const candList = this.stream.getLocalIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (
!!c.candidate &&
(c.candidate.indexOf(finalLocalCandidate.ip) >= 0 || c.candidate.indexOf(finalLocalCandidate.address) >= 0) &&
c.candidate.indexOf(finalLocalCandidate.port) >= 0
);
});
finalLocalCandidate.raw = [];
for (let c of cand) {
finalLocalCandidate.raw.push(c.candidate);
}
} else {
finalLocalCandidate = 'ERROR: No active local ICE candidate. Probably ICE-TCP is being used';
}
let finalRemoteCandidate = remoteCandidates.get(remoteCandidateId);
if (!!finalRemoteCandidate) {
const candList = this.stream.getRemoteIceCandidateList();
const cand = candList.filter((c: RTCIceCandidate) => {
return (
!!c.candidate &&
(c.candidate.indexOf(finalRemoteCandidate.ip) >= 0 || c.candidate.indexOf(finalRemoteCandidate.address) >= 0) &&
c.candidate.indexOf(finalRemoteCandidate.port) >= 0
);
});
finalRemoteCandidate.raw = [];
for (let c of cand) {
finalRemoteCandidate.raw.push(c.candidate);
}
} else {
finalRemoteCandidate = 'ERROR: No active remote ICE candidate. Probably ICE-TCP is being used';
}
return resolve({
localCandidate: finalLocalCandidate,
remoteCandidate: finalRemoteCandidate
});
});
}
public stopWebRtcStats() {
if (this.webRtcStatsEnabled) {
clearInterval(this.webRtcStatsIntervalId);
logger.warn(
'WebRtc stats stopped for disposed stream ' + this.stream.streamId + ' of connection ' + this.stream.connection.connectionId
);
}
}
private async sendStats(url: string, response: JSONStatsResponse): Promise<void> {
try {
const configuration: RequestInit = {
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(response),
method: 'POST'
};
await fetch(url, configuration);
} catch (error) {
logger.error(`sendStats error: ${JSON.stringify(error)}`);
}
}
private async sendStatsToHttpEndpoint(): Promise<void> {
try {
const webrtcStats: IWebrtcStats = await this.getCommonStats();
const response = this.generateJSONStatsResponse(webrtcStats);
await this.sendStats(this.POST_URL, response);
} catch (error) {
logger.log(error);
}
}
// Have been tested in:
// - Linux Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.218
// - Firefox 86
// - Microsoft Edge 91.0.825.0
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Windows Desktop:
// - Chrome 89.0.4389.90
// - Opera 74.0.3911.232
// - Firefox 86.0.1
// - Microsoft Edge 89.0.774.54
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - MacOS Desktop:
// - Chrome 89.0.4389.90
// - Opera 75.0.3969.93
// - Firefox 87.0
// - Microsoft Edge 89.0.774.57
// - Safari 14.0 (14610.1.28.1.9)
// - Electron 11.3.0 (Chromium 87.0.4280.141)
// - Android:
// - Chrome Mobile 89.0.4389.90
// - Opera 62.3.3146.57763
// - Firefox Mobile 86.6.1
// - Microsoft Edge Mobile 46.02.4.5147
// - Ionic 5
// - React Native 0.64
// - iOS:
// - Safari Mobile
// - ¿Ionic?
// - ¿React Native?
public async getCommonStats(): Promise<IWebrtcStats> {
return new Promise(async (resolve, reject) => {
try {
const statsReport: any = await this.stream.getRTCPeerConnection().getStats();
const response: IWebrtcStats = this.getWebRtcStatsResponseOutline();
const videoTrackStats = ['framesReceived', 'framesDropped', 'framesSent', 'frameHeight', 'frameWidth'];
const candidatePairStats = ['availableOutgoingBitrate', 'currentRoundTripTime'];
statsReport.forEach((stat: any) => {
let mediaType = stat.mediaType != null ? stat.mediaType : stat.kind;
const addStat = (direction: string, key: string): void => {
if (stat[key] != null && response[direction] != null) {
if (!mediaType && videoTrackStats.indexOf(key) > -1) {
mediaType = 'video';
}
if (direction != null && mediaType != null && key != null && response[direction][mediaType] != null) {
response[direction][mediaType][key] = Number(stat[key]);
} else if (direction != null && key != null && candidatePairStats.includes(key)) {
// candidate-pair-stats
response[direction][key] = Number(stat[key]);
}
}
};
switch (stat.type) {
case 'outbound-rtp':
addStat('outbound', 'bytesSent');
addStat('outbound', 'packetsSent');
addStat('outbound', 'framesEncoded');
addStat('outbound', 'nackCount');
addStat('outbound', 'firCount');
addStat('outbound', 'pliCount');
addStat('outbound', 'qpSum');
break;
case 'inbound-rtp':
addStat('inbound', 'bytesReceived');
addStat('inbound', 'packetsReceived');
addStat('inbound', 'packetsLost');
addStat('inbound', 'jitter');
addStat('inbound', 'framesDecoded');
addStat('inbound', 'nackCount');
addStat('inbound', 'firCount');
addStat('inbound', 'pliCount');
break;
case 'track':
addStat('inbound', 'jitterBufferDelay');
addStat('inbound', 'framesReceived');
addStat('outbound', 'framesDropped');
addStat('outbound', 'framesSent');
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameHeight');
addStat(this.stream.isLocal() ? 'outbound' : 'inbound', 'frameWidth');
break;
case 'candidate-pair':
addStat('candidatepair', 'currentRoundTripTime');
addStat('candidatepair', 'availableOutgoingBitrate');
break;
}
});
// Delete candidatepair from response if null
if (!response?.candidatepair || Object.keys(<Object>response.candidatepair).length === 0) {
delete response.candidatepair;
}
return resolve(response);
} catch (error) {
logger.error('Error getting common stats: ', error);
return reject(error);
}
});
}
private generateJSONStatsResponse(stats: IWebrtcStats): JSONStatsResponse {
return {
'@timestamp': new Date().toISOString(),
participant_id: this.stream.connection.data,
session_id: this.stream.session.sessionId,
platform: platform.getName(),
platform_description: platform.getDescription(),
stream: 'webRTC',
webrtc_stats: stats
};
}
private getWebRtcStatsResponseOutline(): IWebrtcStats {
if (this.stream.isLocal()) {
return {
outbound: {
audio: {},
video: {}
},
candidatepair: {}
};
} else {
return {
inbound: {
audio: {},
video: {}
}
};
}
}
}

View File

@ -1,51 +0,0 @@
import { JL } from 'jsnlog';
export { OpenVidu } from './OpenVidu/OpenVidu';
export { Session } from './OpenVidu/Session';
export { Publisher } from './OpenVidu/Publisher';
export { Subscriber } from './OpenVidu/Subscriber';
export { StreamManager } from './OpenVidu/StreamManager';
export { Stream } from './OpenVidu/Stream';
export { Connection } from './OpenVidu/Connection';
export { LocalRecorder } from './OpenVidu/LocalRecorder';
export { Filter } from './OpenVidu/Filter';
export { LocalRecorderState } from './OpenViduInternal/Enums/LocalRecorderState';
export { OpenViduError, OpenViduErrorName } from './OpenViduInternal/Enums/OpenViduError';
export { TypeOfVideo } from './OpenViduInternal/Enums/TypeOfVideo';
export { VideoInsertMode } from './OpenViduInternal/Enums/VideoInsertMode';
export { Event } from './OpenViduInternal/Events/Event';
export { ConnectionEvent } from './OpenViduInternal/Events/ConnectionEvent';
export { PublisherSpeakingEvent } from './OpenViduInternal/Events/PublisherSpeakingEvent';
export { RecordingEvent } from './OpenViduInternal/Events/RecordingEvent';
export { SessionDisconnectedEvent } from './OpenViduInternal/Events/SessionDisconnectedEvent';
export { SignalEvent } from './OpenViduInternal/Events/SignalEvent';
export { StreamEvent } from './OpenViduInternal/Events/StreamEvent';
export { StreamManagerEvent } from './OpenViduInternal/Events/StreamManagerEvent';
export { VideoElementEvent } from './OpenViduInternal/Events/VideoElementEvent';
export { StreamPropertyChangedEvent } from './OpenViduInternal/Events/StreamPropertyChangedEvent';
export { ConnectionPropertyChangedEvent } from './OpenViduInternal/Events/ConnectionPropertyChangedEvent';
export { FilterEvent } from './OpenViduInternal/Events/FilterEvent';
export { NetworkQualityLevelChangedEvent } from './OpenViduInternal/Events/NetworkQualityLevelChangedEvent';
export { SpeechToTextEvent } from './OpenViduInternal/Events/SpeechToTextEvent';
export { ExceptionEvent, ExceptionEventName } from './OpenViduInternal/Events/ExceptionEvent';
export { Capabilities } from './OpenViduInternal/Interfaces/Public/Capabilities';
export { Device } from './OpenViduInternal/Interfaces/Public/Device';
export { EventDispatcher } from './OpenVidu/EventDispatcher';
export { OpenViduAdvancedConfiguration } from './OpenViduInternal/Interfaces/Public/OpenViduAdvancedConfiguration';
export { PublisherProperties } from './OpenViduInternal/Interfaces/Public/PublisherProperties';
export { SignalOptions } from './OpenViduInternal/Interfaces/Public/SignalOptions';
export { StreamManagerVideo } from './OpenViduInternal/Interfaces/Public/StreamManagerVideo';
export { SubscriberProperties } from './OpenViduInternal/Interfaces/Public/SubscriberProperties';
export { EventMap } from './OpenViduInternal/Events/EventMap/EventMap';
export { SessionEventMap } from './OpenViduInternal/Events/EventMap/SessionEventMap';
export { StreamManagerEventMap } from './OpenViduInternal/Events/EventMap/StreamManagerEventMap';
export { PublisherEventMap } from './OpenViduInternal/Events/EventMap/PublisherEventMap';
export * from './OpenViduInternal/Events/Types/Types';
// Disable jsnlog when library is loaded
JL.setOptions({ enabled: false });

View File

@ -1,36 +0,0 @@
{
//"allowUnusedLabels": true,
"allowUnreachableCode": false,
"buildOnSave": false,
"compileOnSave": true,
"compilerOptions": {
"allowJs": true,
"allowSyntheticDefaultImports": true,
"emitBOM": false,
"forceConsistentCasingInFileNames": true,
"lib": [
"dom",
"es2015.promise",
"es5",
"scripthost"
],
"module": "commonjs",
"noFallthroughCasesInSwitch": true,
//"noImplicitAny": true,
"noImplicitReturns": true,
"noImplicitThis": true,
//"noUnusedLocals": true,
//"noUnusedParameters": true,
"outDir": "../../lib",
"preserveConstEnums": true,
"removeComments": true,
"rootDir": "./src",
"skipDefaultLibCheck": true,
"skipLibCheck": true,
"sourceMap": true,
"strictNullChecks": true,
"suppressExcessPropertyErrors": true,
"suppressImplicitAnyIndexErrors": true,
"target": "es5"
}
}

View File

@ -1 +0,0 @@
/target/

View File

@ -1,14 +0,0 @@
[![License badge](https://img.shields.io/badge/license-Apache2-orange.svg)](http://www.apache.org/licenses/LICENSE-2.0)
[![OpenVidu Tests](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml/badge.svg)](https://github.com/OpenVidu/openvidu/actions/workflows/openvidu-ce-test.yml)
[![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/)
[![Support badge](https://img.shields.io/badge/support-sof-yellowgreen.svg)](https://openvidu.discourse.group/)
[![][OpenViduLogo]](https://openvidu.io)
openvidu-client
===
Internal Java client used by [openvidu-server](https://github.com/OpenVidu/openvidu/tree/master/openvidu-server). Can be used to implement an Android client.
[OpenViduLogo]: https://secure.gravatar.com/avatar/5daba1d43042f2e4e85849733c8e5702?s=120

View File

@ -1,106 +0,0 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.openvidu</groupId>
<artifactId>openvidu-parent</artifactId>
<version>2.0.0</version>
</parent>
<artifactId>openvidu-client</artifactId>
<version>1.1.0</version>
<packaging>jar</packaging>
<name>OpenVidu Client</name>
<description>
OpenVidu client library for the client-side of OpenVidu Server
</description>
<url>https://github.com/OpenVidu/openvidu</url>
<licenses>
<license>
<name>Apache 2.0</name>
<url>http://www.apache.org/licenses/LICENSE-2.0</url>
<distribution>repo</distribution>
</license>
</licenses>
<organization>
<name>OpenVidu</name>
<url>https://github.com/OpenVidu/openvidu</url>
</organization>
<scm>
<url>${openvidu.scm.url}</url>
<connection>scm:git:${openvidu.scm.connection}</connection>
<developerConnection>scm:git:${openvidu.scm.connection}</developerConnection>
<tag>develop</tag>
</scm>
<developers>
<developer>
<id>openvidu.io</id>
<name>-openvidu.io Community</name>
<organization>OpenVidu</organization>
<organizationUrl>https://openvidu.io</organizationUrl>
</developer>
</developers>
<dependencies>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client</artifactId>
<version>${version.kurento}</version>
</dependency>
<dependency>
<groupId>org.kurento</groupId>
<artifactId>kurento-jsonrpc-client-jetty</artifactId>
<version>${version.kurento}</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${version.junit}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${version.mockito.core}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>default</id>
<activation>
<property>
<name>default</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<skipTests>true</skipTests>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -1,217 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client;
import static io.openvidu.client.internal.ProtocolElements.CUSTOMREQUEST_METHOD;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERID_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERSTREAMID_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_PEERSTREAMS_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_ROOM_PARAM;
import static io.openvidu.client.internal.ProtocolElements.JOINROOM_USER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.LEAVEROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_CANDIDATE_PARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_EPNAME_PARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_METHOD;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_SDPMIDPARAM;
import static io.openvidu.client.internal.ProtocolElements.ONICECANDIDATE_SDPMLINEINDEX_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_DOLOOPBACK_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_SDPANSWER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.PUBLISHVIDEO_SDPOFFER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SDPANSWER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SDPOFFER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.RECEIVEVIDEO_SENDER_PARAM;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_MESSAGE_PARAM;
import static io.openvidu.client.internal.ProtocolElements.SENDMESSAGE_ROOM_METHOD;
import static io.openvidu.client.internal.ProtocolElements.UNPUBLISHVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.UNSUBSCRIBEFROMVIDEO_METHOD;
import static io.openvidu.client.internal.ProtocolElements.UNSUBSCRIBEFROMVIDEO_SENDER_PARAM;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.kurento.jsonrpc.client.JsonRpcClient;
import org.kurento.jsonrpc.client.JsonRpcClientWebSocket;
import org.kurento.jsonrpc.client.JsonRpcWSConnectionListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.internal.JsonRoomUtils;
import io.openvidu.client.internal.Notification;
/**
* Java client for the room server.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class OpenViduClient {
private static final Logger log = LoggerFactory.getLogger(OpenViduClient.class);
private JsonRpcClient client;
private ServerJsonRpcHandler handler;
public OpenViduClient(String wsUri) {
this(new JsonRpcClientWebSocket(wsUri, new JsonRpcWSConnectionListener() {
@Override
public void reconnected(boolean sameServer) {
}
@Override
public void disconnected() {
log.warn("JsonRpcWebsocket connection: Disconnected");
}
@Override
public void connectionFailed() {
log.warn("JsonRpcWebsocket connection: Connection failed");
}
@Override
public void connected() {
}
@Override
public void reconnecting() {
log.warn("JsonRpcWebsocket connection: is reconnecting");
}
}, new SslContextFactory(true)));
}
public OpenViduClient(JsonRpcClient client) {
this.client = client;
this.handler = new ServerJsonRpcHandler();
this.client.setServerRequestHandler(this.handler);
}
public OpenViduClient(JsonRpcClient client, ServerJsonRpcHandler handler) {
this.client = client;
this.handler = handler;
this.client.setServerRequestHandler(this.handler);
}
public void close() throws IOException {
this.client.close();
}
public Map<String, List<String>> joinRoom(String roomName, String userName)
throws IOException {
JsonObject params = new JsonObject();
params.addProperty(JOINROOM_ROOM_PARAM, roomName);
params.addProperty(JOINROOM_USER_PARAM, userName);
JsonElement result = client.sendRequest(JOINROOM_METHOD, params);
Map<String, List<String>> peers = new HashMap<String, List<String>>();
JsonArray jsonPeers = JsonRoomUtils.getResponseProperty(result, "value", JsonArray.class);
if (jsonPeers.size() > 0) {
Iterator<JsonElement> peerIt = jsonPeers.iterator();
while (peerIt.hasNext()) {
JsonElement peer = peerIt.next();
String peerId = JsonRoomUtils.getResponseProperty(peer, JOINROOM_PEERID_PARAM,
String.class);
List<String> streams = new ArrayList<String>();
JsonArray jsonStreams = JsonRoomUtils.getResponseProperty(peer, JOINROOM_PEERSTREAMS_PARAM,
JsonArray.class, true);
if (jsonStreams != null) {
Iterator<JsonElement> streamIt = jsonStreams.iterator();
while (streamIt.hasNext()) {
streams.add(JsonRoomUtils.getResponseProperty(streamIt.next(),
JOINROOM_PEERSTREAMID_PARAM, String.class));
}
}
peers.put(peerId, streams);
}
}
return peers;
}
public void leaveRoom() throws IOException {
client.sendRequest(LEAVEROOM_METHOD, new JsonObject());
}
public String publishVideo(String sdpOffer, boolean doLoopback) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(PUBLISHVIDEO_SDPOFFER_PARAM, sdpOffer);
params.addProperty(PUBLISHVIDEO_DOLOOPBACK_PARAM, doLoopback);
JsonElement result = client.sendRequest(PUBLISHVIDEO_METHOD, params);
return JsonRoomUtils.getResponseProperty(result, PUBLISHVIDEO_SDPANSWER_PARAM, String.class);
}
public void unpublishVideo() throws IOException {
client.sendRequest(UNPUBLISHVIDEO_METHOD, new JsonObject());
}
// sender should look like 'username_streamId'
public String receiveVideoFrom(String sender, String sdpOffer) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(RECEIVEVIDEO_SENDER_PARAM, sender);
params.addProperty(RECEIVEVIDEO_SDPOFFER_PARAM, sdpOffer);
JsonElement result = client.sendRequest(RECEIVEVIDEO_METHOD, params);
return JsonRoomUtils.getResponseProperty(result, RECEIVEVIDEO_SDPANSWER_PARAM, String.class);
}
// sender should look like 'username_streamId'
public void unsubscribeFromVideo(String sender) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(UNSUBSCRIBEFROMVIDEO_SENDER_PARAM, sender);
client.sendRequest(UNSUBSCRIBEFROMVIDEO_METHOD, params);
}
public void onIceCandidate(String endpointName, String candidate, String sdpMid,
int sdpMLineIndex) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(ONICECANDIDATE_EPNAME_PARAM, endpointName);
params.addProperty(ONICECANDIDATE_CANDIDATE_PARAM, candidate);
params.addProperty(ONICECANDIDATE_SDPMIDPARAM, sdpMid);
params.addProperty(ONICECANDIDATE_SDPMLINEINDEX_PARAM, sdpMLineIndex);
client.sendRequest(ONICECANDIDATE_METHOD, params);
}
public void sendMessage(String userName, String roomName, String message) throws IOException {
JsonObject params = new JsonObject();
params.addProperty(SENDMESSAGE_MESSAGE_PARAM, message);
client.sendRequest(SENDMESSAGE_ROOM_METHOD, params);
}
public JsonElement customRequest(JsonObject customReqParams) throws IOException {
return client.sendRequest(CUSTOMREQUEST_METHOD, customReqParams);
}
/**
* Polls the notifications list maintained by this client to obtain new events sent by server.
* This method blocks until there is a notification to return. This is a one-time operation for
* the returned element.
*
* @return a server notification object, null when interrupted while waiting
*/
public Notification getServerNotification() {
return this.handler.getNotification();
}
}

View File

@ -1,88 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package io.openvidu.client;
import org.kurento.jsonrpc.JsonRpcErrorException;
public class OpenViduException extends JsonRpcErrorException {
private static final long serialVersionUID = 1L;
public static enum Code {
GENERIC_ERROR_CODE(999), WRONG_PATH_CODE(998), WRONG_OPENVIDU_EDITION_ERROR_CODE(997),
SERVICE_NOT_ENABLED_ERROR_CODE(996),
TRANSPORT_ERROR_CODE(803), TRANSPORT_RESPONSE_ERROR_CODE(802), TRANSPORT_REQUEST_ERROR_CODE(801),
MEDIA_TYPE_STREAM_INCOMPATIBLE_WITH_RECORDING_PROPERTIES_ERROR_CODE(309),
MEDIA_TYPE_RECORDING_PROPERTIES_ERROR_CODE(308), MEDIA_MUTE_ERROR_CODE(307),
MEDIA_NOT_A_WEB_ENDPOINT_ERROR_CODE(306), MEDIA_RTP_ENDPOINT_ERROR_CODE(305),
MEDIA_WEBRTC_ENDPOINT_ERROR_CODE(304), MEDIA_ENDPOINT_ERROR_CODE(303), MEDIA_SDP_ERROR_CODE(302),
MEDIA_GENERIC_ERROR_CODE(301),
ROOM_CANNOT_BE_CREATED_ERROR_CODE(204), ROOM_CLOSED_ERROR_CODE(203), ROOM_NOT_FOUND_ERROR_CODE(202),
ROOM_GENERIC_ERROR_CODE(201),
USER_ALREADY_STREAMING_ERROR_CODE(106), USER_NOT_STREAMING_ERROR_CODE(105),
EXISTING_USER_IN_ROOM_ERROR_CODE(104), USER_CLOSED_ERROR_CODE(103), USER_NOT_FOUND_ERROR_CODE(102),
USER_GENERIC_ERROR_CODE(10),
USER_UNAUTHORIZED_ERROR_CODE(401), ROLE_NOT_FOUND_ERROR_CODE(402), SESSIONID_CANNOT_BE_CREATED_ERROR_CODE(403),
TOKEN_CANNOT_BE_CREATED_ERROR_CODE(404), EXISTING_FILTER_ALREADY_APPLIED_ERROR_CODE(405),
FILTER_NOT_APPLIED_ERROR_CODE(406), FILTER_EVENT_LISTENER_NOT_FOUND_ERROR_CODE(407),
PUBLISHER_ENDPOINT_NOT_FOUND_ERROR_CODE(408),
USER_METADATA_FORMAT_INVALID_ERROR_CODE(500),
SIGNAL_FORMAT_INVALID_ERROR_CODE(600), SIGNAL_TO_INVALID_ERROR_CODE(601),
BROADCAST_CONCURRENT_ERROR_CODE(711), BROADCAST_START_ERROR_CODE(710), DOCKER_NOT_FOUND(709), RECORDING_PATH_NOT_VALID(708), RECORDING_FILE_EMPTY_ERROR(707),
RECORDING_DELETE_ERROR_CODE(706), RECORDING_LIST_ERROR_CODE(705), RECORDING_STOP_ERROR_CODE(704),
RECORDING_START_ERROR_CODE(703), RECORDING_REPORT_ERROR_CODE(702), RECORDING_COMPLETION_ERROR_CODE(701),
FORCED_CODEC_NOT_FOUND_IN_SDPOFFER(800),
MEDIA_NODE_NOT_FOUND(900), MEDIA_NODE_STATUS_WRONG(901), MEDIA_NODE_CONNECTION_ERROR_CODE(902);
private int value;
private Code(int value) {
this.value = value;
}
public int getValue() {
return this.value;
}
}
private Code code = Code.GENERIC_ERROR_CODE;
public OpenViduException(Code code, String message) {
super(code.getValue(), message);
this.code = code;
}
public int getCodeValue() {
return code.getValue();
}
@Override
public String toString() {
return super.toString();
}
}

View File

@ -1,222 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import org.kurento.jsonrpc.DefaultJsonRpcHandler;
import org.kurento.jsonrpc.Transaction;
import org.kurento.jsonrpc.message.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.internal.IceCandidate;
import io.openvidu.client.internal.IceCandidateInfo;
import io.openvidu.client.internal.JsonRoomUtils;
import io.openvidu.client.internal.MediaErrorInfo;
import io.openvidu.client.internal.Notification;
import io.openvidu.client.internal.ParticipantEvictedInfo;
import io.openvidu.client.internal.ParticipantJoinedInfo;
import io.openvidu.client.internal.ParticipantLeftInfo;
import io.openvidu.client.internal.ParticipantPublishedInfo;
import io.openvidu.client.internal.ParticipantUnpublishedInfo;
import io.openvidu.client.internal.ProtocolElements;
import io.openvidu.client.internal.RoomClosedInfo;
import io.openvidu.client.internal.SendMessageInfo;
/**
* Service that handles server JSON-RPC events.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ServerJsonRpcHandler extends DefaultJsonRpcHandler<JsonObject> {
private static final Logger log = LoggerFactory.getLogger(ServerJsonRpcHandler.class);
private BlockingQueue<Notification> notifications = new ArrayBlockingQueue<Notification>(100);
@Override
public void handleRequest(Transaction transaction, Request<JsonObject> request) throws Exception {
Notification notif = null;
try {
switch (request.getMethod()) {
case ProtocolElements.ICECANDIDATE_METHOD:
notif = iceCandidate(transaction, request);
break;
case ProtocolElements.MEDIAERROR_METHOD:
notif = mediaError(transaction, request);
break;
case ProtocolElements.PARTICIPANTJOINED_METHOD:
notif = participantJoined(transaction, request);
break;
case ProtocolElements.PARTICIPANTLEFT_METHOD:
notif = participantLeft(transaction, request);
break;
case ProtocolElements.PARTICIPANTEVICTED_METHOD:
notif = participantEvicted(transaction, request);
break;
case ProtocolElements.PARTICIPANTPUBLISHED_METHOD:
notif = participantPublished(transaction, request);
break;
case ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD:
notif = participantUnpublished(transaction, request);
break;
case ProtocolElements.ROOMCLOSED_METHOD:
notif = roomClosed(transaction, request);
break;
case ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD:
notif = participantSendMessage(transaction, request);
break;
default:
throw new Exception("Unrecognized request " + request.getMethod());
}
} catch (Exception e) {
log.error("Exception processing request {}", request, e);
transaction.sendError(e);
return;
}
if (notif != null) {
try {
notifications.put(notif);
log.debug("Enqueued notification {}", notif);
} catch (InterruptedException e) {
log.warn("Interrupted when enqueuing notification {}", notif, e);
}
}
}
private Notification participantSendMessage(Transaction transaction,
Request<JsonObject> request) {
String data = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_DATA_PARAM, String.class);
String from = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_FROM_PARAM, String.class);
String type = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTSENDMESSAGE_TYPE_PARAM, String.class);
SendMessageInfo eventInfo = new SendMessageInfo(data, from, type);
log.debug("Recvd send message event {}", eventInfo);
return eventInfo;
}
private Notification roomClosed(Transaction transaction, Request<JsonObject> request) {
String room = JsonRoomUtils.getRequestParam(request, ProtocolElements.ROOMCLOSED_ROOM_PARAM,
String.class);
RoomClosedInfo eventInfo = new RoomClosedInfo(room);
log.debug("Recvd room closed event {}", eventInfo);
return eventInfo;
}
private Notification participantUnpublished(Transaction transaction,
Request<JsonObject> request) {
String name = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTUNPUBLISHED_NAME_PARAM, String.class);
ParticipantUnpublishedInfo eventInfo = new ParticipantUnpublishedInfo(name);
log.debug("Recvd participant unpublished event {}", eventInfo);
return eventInfo;
}
private Notification participantPublished(Transaction transaction, Request<JsonObject> request) {
String id = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTPUBLISHED_USER_PARAM, String.class);
JsonArray jsonStreams = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTPUBLISHED_STREAMS_PARAM, JsonArray.class);
Iterator<JsonElement> streamIt = jsonStreams.iterator();
List<String> streams = new ArrayList<String>();
while (streamIt.hasNext()) {
streams.add(JsonRoomUtils.getResponseProperty(streamIt.next(),
ProtocolElements.PARTICIPANTPUBLISHED_STREAMID_PARAM, String.class));
}
ParticipantPublishedInfo eventInfo = new ParticipantPublishedInfo(id, streams);
log.debug("Recvd published event {}", eventInfo);
return eventInfo;
}
private Notification participantEvicted(Transaction transaction, Request<JsonObject> request) {
ParticipantEvictedInfo eventInfo = new ParticipantEvictedInfo();
log.debug("Recvd participant evicted event {}", eventInfo);
return eventInfo;
}
private Notification participantLeft(Transaction transaction, Request<JsonObject> request) {
String name = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTLEFT_NAME_PARAM, String.class);
ParticipantLeftInfo eventInfo = new ParticipantLeftInfo(name);
log.debug("Recvd participant left event {}", eventInfo);
return eventInfo;
}
private Notification participantJoined(Transaction transaction, Request<JsonObject> request) {
String id = JsonRoomUtils.getRequestParam(request,
ProtocolElements.PARTICIPANTJOINED_USER_PARAM, String.class);
ParticipantJoinedInfo eventInfo = new ParticipantJoinedInfo(id);
log.debug("Recvd participant joined event {}", eventInfo);
return eventInfo;
}
private Notification mediaError(Transaction transaction, Request<JsonObject> request) {
String description = JsonRoomUtils.getRequestParam(request,
ProtocolElements.MEDIAERROR_ERROR_PARAM, String.class);
MediaErrorInfo eventInfo = new MediaErrorInfo(description);
log.debug("Recvd media error event {}", eventInfo);
return eventInfo;
}
private Notification iceCandidate(Transaction transaction, Request<JsonObject> request) {
String candidate = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_CANDIDATE_PARAM, String.class);
String sdpMid = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_SDPMID_PARAM, String.class);
int sdpMLineIndex = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_SDPMLINEINDEX_PARAM, Integer.class);
IceCandidate iceCandidate = new IceCandidate(candidate, sdpMid, sdpMLineIndex);
String endpoint = JsonRoomUtils.getRequestParam(request,
ProtocolElements.ICECANDIDATE_EPNAME_PARAM, String.class);
IceCandidateInfo eventInfo = new IceCandidateInfo(iceCandidate, endpoint);
log.debug("Recvd ICE candidate event {}", eventInfo);
return eventInfo;
}
/**
* Blocks until an element is available and then returns it by removing it from the queue.
*
* @return a {@link Notification} from the queue, null when interrupted
* @see BlockingQueue#take()
*/
public Notification getNotification() {
try {
Notification notif = notifications.take();
log.debug("Dequeued notification {}", notif);
return notif;
} catch (InterruptedException e) {
log.info("Interrupted while polling notifications' queue");
return null;
}
}
}

View File

@ -1,33 +0,0 @@
package io.openvidu.client.internal;
public class IceCandidate {
private String candidate;
private String sdpMid;
private int sdpMLineIndex;
public IceCandidate(String candidate, String sdpMid, int sdpMLineIndex) {
super();
this.candidate = candidate;
this.sdpMid = sdpMid;
this.sdpMLineIndex = sdpMLineIndex;
}
public String getCandidate() {
return candidate;
}
public String getSdpMid() {
return sdpMid;
}
public int getSdpMLineIndex() {
return sdpMLineIndex;
}
@Override
public String toString() {
return "IceCandidate [candidate=" + candidate + ", sdpMid=" + sdpMid + ", sdpMLineIndex="
+ sdpMLineIndex + "]";
}
}

View File

@ -1,70 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class IceCandidateInfo extends Notification {
private IceCandidate iceCandidate;
private String endpointName;
public IceCandidateInfo(IceCandidate iceCandidate, String endpointName) {
super(ProtocolElements.ICECANDIDATE_METHOD);
this.iceCandidate = iceCandidate;
this.endpointName = endpointName;
}
public IceCandidate getIceCandidate() {
return iceCandidate;
}
public void setIceCandidate(IceCandidate iceCandidate) {
this.iceCandidate = iceCandidate;
}
public String getEndpointName() {
return endpointName;
}
public void setEndpointName(String endpointName) {
this.endpointName = endpointName;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (endpointName != null) {
builder.append("endpointName=").append(endpointName).append(", ");
}
if (iceCandidate != null) {
builder.append("iceCandidate=[sdpMLineIndex= ").append(iceCandidate.getSdpMLineIndex())
.append(", sdpMid=").append(iceCandidate.getSdpMid()).append(", candidate=")
.append(iceCandidate.getCandidate()).append("]");
}
builder.append("]");
return builder.toString();
}
}

View File

@ -1,107 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
import org.kurento.jsonrpc.message.Request;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.openvidu.client.OpenViduException;
import io.openvidu.client.OpenViduException.Code;
/**
* JSON tools for extracting info from request or response elements.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class JsonRoomUtils {
public static <T> T getRequestParam(Request<JsonObject> request, String paramName, Class<T> type) {
return getRequestParam(request, paramName, type, false);
}
public static <T> T getRequestParam(Request<JsonObject> request, String paramName, Class<T> type,
boolean allowNull) {
JsonObject params = request.getParams();
if (params == null) {
if (!allowNull) {
throw new OpenViduException(Code.TRANSPORT_REQUEST_ERROR_CODE,
"Invalid request lacking parameter '" + paramName + "'");
} else {
return null;
}
}
return getConverted(params.get(paramName), paramName, type, allowNull);
}
public static <T> T getResponseProperty(JsonElement result, String property, Class<T> type) {
return getResponseProperty(result, property, type, false);
}
public static <T> T getResponseProperty(JsonElement result, String property, Class<T> type,
boolean allowNull) {
if (!(result instanceof JsonObject)) {
throw new OpenViduException(Code.TRANSPORT_RESPONSE_ERROR_CODE,
"Invalid response format. The response '" + result + "' should be a Json object");
}
return getConverted(result.getAsJsonObject().get(property), property, type, allowNull);
}
public static JsonArray getResponseArray(JsonElement result) {
if (!result.isJsonArray()) {
throw new OpenViduException(Code.TRANSPORT_RESPONSE_ERROR_CODE,
"Invalid response format. The response '" + result + "' should be a Json array");
}
return result.getAsJsonArray();
}
@SuppressWarnings("unchecked")
private static <T> T getConverted(JsonElement paramValue, String property, Class<T> type,
boolean allowNull) {
if (paramValue == null) {
if (allowNull) {
return null;
} else {
throw new OpenViduException(Code.TRANSPORT_ERROR_CODE, "Invalid method lacking parameter '"
+ property + "'");
}
}
if (type == String.class) {
if (paramValue.isJsonPrimitive()) {
return (T) paramValue.getAsString();
}
}
if (type == Integer.class) {
if (paramValue.isJsonPrimitive()) {
return (T) Integer.valueOf(paramValue.getAsInt());
}
}
if (type == JsonArray.class) {
if (paramValue.isJsonArray()) {
return (T) paramValue.getAsJsonArray();
}
}
throw new OpenViduException(Code.TRANSPORT_ERROR_CODE, "Param '" + property + "' with value '"
+ paramValue + "' is not a " + type.getName());
}
}

View File

@ -1,55 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*
* @see Notification
*/
public class MediaErrorInfo extends Notification {
private String description;
public MediaErrorInfo(String description) {
super(ProtocolElements.MEDIAERROR_METHOD);
this.description = description;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (description != null) {
builder.append("description=").append(description);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -1,90 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* Wrapper for server events.
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public abstract class Notification {
public enum Method {
ICECANDIDATE_METHOD(ProtocolElements.ICECANDIDATE_METHOD), MEDIAERROR_METHOD(
ProtocolElements.MEDIAERROR_METHOD), PARTICIPANTJOINED_METHOD(
ProtocolElements.PARTICIPANTJOINED_METHOD), PARTICIPANTLEFT_METHOD(
ProtocolElements.PARTICIPANTLEFT_METHOD), PARTICIPANTEVICTED_METHOD(
ProtocolElements.PARTICIPANTEVICTED_METHOD), PARTICIPANTPUBLISHED_METHOD(
ProtocolElements.PARTICIPANTPUBLISHED_METHOD), PARTICIPANTUNPUBLISHED_METHOD(
ProtocolElements.PARTICIPANTUNPUBLISHED_METHOD), ROOMCLOSED_METHOD(
ProtocolElements.ROOMCLOSED_METHOD), PARTICIPANTSENDMESSAGE_METHOD(
ProtocolElements.PARTICIPANTSENDMESSAGE_METHOD);
private String methodValue;
private Method(String val) {
this.methodValue = val;
}
public String getMethodValue() {
return methodValue;
}
public static Method getFromValue(String val) {
for (Method m : Method.values()) {
if (m.methodValue.equals(val)) {
return m;
}
}
return null;
}
@Override
public String toString() {
return getMethodValue().toString();
}
}
private Method method;
public Notification(Method method) {
this.setMethod(method);
}
public Notification(String methodValue) {
this(Method.getFromValue(methodValue));
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (method != null) {
builder.append("method=").append(method);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -1,30 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantEvictedInfo extends Notification {
public ParticipantEvictedInfo() {
super(ProtocolElements.PARTICIPANTEVICTED_METHOD);
}
}

View File

@ -1,54 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantJoinedInfo extends Notification {
private String id;
public ParticipantJoinedInfo(String id) {
super(ProtocolElements.PARTICIPANTJOINED_METHOD);
this.id = id;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (id != null) {
builder.append("id=").append(id);
}
builder.append("]");
return builder.toString();
}
}

View File

@ -1,54 +0,0 @@
/*
* (C) Copyright 2017-2022 OpenVidu (https://openvidu.io)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.openvidu.client.internal;
/**
* @see Notification
*
* @author <a href="mailto:rvlad@naevatec.com">Radu Tom Vlad</a>
*/
public class ParticipantLeftInfo extends Notification {
private String name;
public ParticipantLeftInfo(String name) {
super(ProtocolElements.PARTICIPANTLEFT_METHOD);
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("[");
if (getMethod() != null) {
builder.append("method=").append(getMethod()).append(", ");
}
if (name != null) {
builder.append("name=").append(name);
}
builder.append("]");
return builder.toString();
}
}

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