This commit is contained in:
2024-04-21 14:42:52 +02:00
parent 4b69674ede
commit 8a25f53c99
10700 changed files with 55767 additions and 14201 deletions

3
Video/FreeTube/LICENSE Normal file
View File

@ -0,0 +1,3 @@
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work.

66
Video/FreeTube/README.md Normal file
View File

@ -0,0 +1,66 @@
![FreeTube](./img/logo-FreeTube.png)
URL :
# FreeTube
# Téléchargement, Configuration et Lancement
## Téléchargement de FreeTube
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\FreeTube
```
## Modifier la configuration de FreeTube
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\FreeTube
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de FreeTube
Pour utiliser FreeTube tout seul
```bash
docker compose up -d
```
Pour utiliser FreeTube avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://FreeTube.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,66 @@
![Fichier-Stockage](./img/logo-Fichier-Stockage.png)
URL : HHHHH
# Fichier-Stockage
GGGGG
# Téléchargement, Configuration et Lancement
## Téléchargement de Fichier-Stockage
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
## Modifier la configuration de Fichier-Stockage
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Fichier-Stockage
Pour utiliser Fichier-Stockage tout seul
```bash
docker compose up -d
```
Pour utiliser Fichier-Stockage avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Fichier-Stockage.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,8 @@
#### NETWORKS
networks:
back_network:
driver: bridge
attachable: true
#### SERVICES
services:

3
Video/Frigate/LICENSE Normal file
View File

@ -0,0 +1,3 @@
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work.

66
Video/Frigate/README.md Normal file
View File

@ -0,0 +1,66 @@
![Frigate](./img/logo-Frigate.png)
URL :
# Frigate
# Téléchargement, Configuration et Lancement
## Téléchargement de Frigate
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\Frigate
```
## Modifier la configuration de Frigate
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\Frigate
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Frigate
Pour utiliser Frigate tout seul
```bash
docker compose up -d
```
Pour utiliser Frigate avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Frigate.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

96
Video/Frigate/config.yml Normal file
View File

@ -0,0 +1,96 @@
cameras:
garden:
ffmpeg:
inputs:
- path: rtsp://cameraIP:554/s0
roles:
- detect
- rtmp
detect:
width: 1920 # <---- update for your camera's resolution
height: 1080 # <---- update for your camera's resolution
fps: 25
garage:
ffmpeg:
inputs:
- path: rtsp://cameraIP:554/s0
roles:
- detect
- rtmp
detect:
width: 1920 # <---- update for your camera's resolution
height: 1080 # <---- update for your camera's resolution
fps: 25
motion:
mask:
- 0,0,1920,0,1920,393,297,241,314,464,0,541
# Optional: Database configuration
database:
# The path to store the SQLite DB (default: shown below)
path: /media/frigate/frigate.db
detectors:
coral1:
type: edgetpu
device: pci:0
coral2:
type: edgetpu
device: pci:1
objects:
track:
- person
- dog
- cat
- bird
snapshots:
enabled: True
timestamp: true
bounding_box: true
retain:
default: 30
mqtt:
host: 192.168.200.14
ffmpeg:
#hwaccel_args:
# - -hwaccel
# - vaapi
# - -hwaccel_device
# - /dev/dri/renderD128
# - -hwaccel_output_format
# - yuv420p
output_args:
record: -f segment -segment_time 10 -segment_format mp4 -reset_timestamps 1 -strftime 1 -c:v copy -c:a aac
record:
enabled: True
events:
pre_capture: 5
post_capture: 5
retain:
default: 30
mode: active_objects
objects:
- person
- bird
- cat
- dog
birdseye:
# Optional: Enable birdseye view (default: shown below)
enabled: True
# Optional: Width of the output resolution (default: shown below)
width: 1280
# Optional: Height of the output resolution (default: shown below)
height: 720
# Optional: Encoding quality of the mpeg1 feed (default: shown below)
# 1 is the highest quality, and 31 is the lowest. Lower quality feeds utilize less CPU resources.
quality: 8
# Optional: Mode of the view. Available options are: objects, motion, and continuous
# objects - cameras are included if they have had a tracked object within the last 30 seconds
# motion - cameras are included if motion was detected in the last 30 seconds
# continuous - all cameras are included always
mode: continuous

View File

@ -0,0 +1,66 @@
![Fichier-Stockage](./img/logo-Fichier-Stockage.png)
URL : HHHHH
# Fichier-Stockage
GGGGG
# Téléchargement, Configuration et Lancement
## Téléchargement de Fichier-Stockage
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
## Modifier la configuration de Fichier-Stockage
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Fichier-Stockage
Pour utiliser Fichier-Stockage tout seul
```bash
docker compose up -d
```
Pour utiliser Fichier-Stockage avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Fichier-Stockage.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,46 @@
version: "3.9"
services:
frigate:
container_name: frigate
# privileged: true # this may not be necessary for all setups
restart: unless-stopped
image: ghcr.io/blakeblackshear/frigate:stable
devices:
#- /dev/bus/usb:/dev/bus/usb
- /dev/apex_0:/dev/apex_0
- /dev/apex_1:/dev/apex_1
#- /dev/dri/renderD128:/dev/dri/renderD128 # for intel hwaccel, needs to be updated for your hardware
volumes:
- /etc/localtime:/etc/localtime:ro
- /home/ubuntu/docker/frigate/config.yml:/config/config.yml:ro
- /home/ubuntu/freenas/Frigate/media/clips:/media/frigate/clips
- /home/ubuntu/freenas/Frigate/media/recordings:/media/frigate/recordings
- /home/ubuntu/docker/frigate/database:/media/frigate
- type: tmpfs # Optional: 1GB of memory, reduces SSD/SD Card wear
target: /tmp/cache
tmpfs:
size: 2000000000
ports:
- "5000:5000"
- "1935:1935" # RTMP feeds (deprecated)
- "8554:8554" # RTSP feeds
environment:
FRIGATE_RTSP_PASSWORD: "password"
security_opt:
- no-new-privileges:true
labels:
- "traefik.enable=true"
- "traefik.http.routers.frigate.entrypoints=http"
- "traefik.http.routers.frigate.rule=Host(`frigate.jimsgarage.co.uk`)"
- "traefik.http.middlewares.frigate-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.frigate.middlewares=frigate-https-redirect"
- "traefik.http.routers.frigate-secure.entrypoints=https"
- "traefik.http.routers.frigate-secure.rule=Host(`frigate.jimsgarage.co.uk`)"
- "traefik.http.routers.frigate-secure.tls=true"
- "traefik.http.routers.frigate-secure.service=frigate"
- "traefik.http.services.frigate.loadbalancer.server.port=5000"
- "traefik.docker.network=proxy"
networks:
proxy:
external: true

View File

@ -0,0 +1,8 @@
#### NETWORKS
networks:
back_network:
driver: bridge
attachable: true
#### SERVICES
services:

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

218
Video/Jitsi Meet/.env Normal file
View File

@ -0,0 +1,218 @@
# shellcheck disable=SC2034
################################################################################
################################################################################
# Welcome to the Jitsi Meet Docker setup!
#
# This sample .env file contains some basic options to get you started.
# The full options reference can be found here:
# https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker
################################################################################
################################################################################
#
# Basic configuration options
#
# Directory where all configuration will be stored
CONFIG=~/.jitsi-meet-cfg
# Exposed HTTP port
HTTP_PORT=8000
# Exposed HTTPS port
HTTPS_PORT=8443
# System time zone
TZ=UTC
# Public URL for the web service (required)
PUBLIC_URL=https://meet.yourdomain.com
# Media IP addresses to advertise by the JVB
# This setting deprecates DOCKER_HOST_ADDRESS, and supports a comma separated list of IPs
# See the "Running behind NAT or on a LAN environment" section in the Handbook:
# https://jitsi.github.io/handbook/docs/devops-guide/devops-guide-docker#running-behind-nat-or-on-a-lan-environment
JVB_ADVERTISE_IPS=192.168.x.x # Add your Docker Host IP here
#
# JaaS Components (beta)
# https://jaas.8x8.vc
#
# Enable JaaS Components (hosted Jigasi)
# NOTE: if Let's Encrypt is enabled a JaaS account will be automatically created, using the provided email in LETSENCRYPT_EMAIL
#ENABLE_JAAS_COMPONENTS=0
#
# Let's Encrypt configuration
#
# Enable Let's Encrypt certificate generation
#ENABLE_LETSENCRYPT=1
# Domain for which to generate the certificate
#LETSENCRYPT_DOMAIN=meet.example.com
# E-Mail for receiving important account notifications (mandatory)
#LETSENCRYPT_EMAIL=alice@atlanta.net
# Use the staging server (for avoiding rate limits while testing)
#LETSENCRYPT_USE_STAGING=1
#
# Etherpad integration (for document sharing)
#
# Set etherpad-lite URL in docker local network (uncomment to enable)
#ETHERPAD_URL_BASE=http://etherpad.meet.jitsi:9001
# Set etherpad-lite public URL, including /p/ pad path fragment (uncomment to enable)
#ETHERPAD_PUBLIC_URL=https://etherpad.my.domain/p/
# Name your etherpad instance!
ETHERPAD_TITLE=Video Chat
# The default text of a pad
ETHERPAD_DEFAULT_PAD_TEXT="Welcome to Web Chat!\n\n"
# Name of the skin for etherpad
ETHERPAD_SKIN_NAME=colibris
# Skin variants for etherpad
ETHERPAD_SKIN_VARIANTS="super-light-toolbar super-light-editor light-background full-width-editor"
#
# Basic Jigasi configuration options (needed for SIP gateway support)
#
# SIP URI for incoming / outgoing calls
#JIGASI_SIP_URI=test@sip2sip.info
# Password for the specified SIP account as a clear text
#JIGASI_SIP_PASSWORD=passw0rd
# SIP server (use the SIP account domain if in doubt)
#JIGASI_SIP_SERVER=sip2sip.info
# SIP server port
#JIGASI_SIP_PORT=5060
# SIP server transport
#JIGASI_SIP_TRANSPORT=UDP
#
# Authentication configuration (see handbook for details)
#
# Enable authentication
ENABLE_AUTH=1
# Enable guest access
ENABLE_GUESTS=1
# Select authentication type: internal, jwt, ldap or matrix
AUTH_TYPE=internal
# JWT authentication
#
# Application identifier
#JWT_APP_ID=my_jitsi_app_id
# Application secret known only to your token generator
#JWT_APP_SECRET=my_jitsi_app_secret
# (Optional) Set asap_accepted_issuers as a comma separated list
#JWT_ACCEPTED_ISSUERS=my_web_client,my_app_client
# (Optional) Set asap_accepted_audiences as a comma separated list
#JWT_ACCEPTED_AUDIENCES=my_server1,my_server2
# LDAP authentication (for more information see the Cyrus SASL saslauthd.conf man page)
#
# LDAP url for connection
#LDAP_URL=ldaps://ldap.domain.com/
# LDAP base DN. Can be empty
#LDAP_BASE=DC=example,DC=domain,DC=com
# LDAP user DN. Do not specify this parameter for the anonymous bind
#LDAP_BINDDN=CN=binduser,OU=users,DC=example,DC=domain,DC=com
# LDAP user password. Do not specify this parameter for the anonymous bind
#LDAP_BINDPW=LdapUserPassw0rd
# LDAP filter. Tokens example:
# %1-9 - if the input key is user@mail.domain.com, then %1 is com, %2 is domain and %3 is mail
# %s - %s is replaced by the complete service string
# %r - %r is replaced by the complete realm string
#LDAP_FILTER=(sAMAccountName=%u)
# LDAP authentication method
#LDAP_AUTH_METHOD=bind
# LDAP version
#LDAP_VERSION=3
# LDAP TLS using
#LDAP_USE_TLS=1
# List of SSL/TLS ciphers to allow
#LDAP_TLS_CIPHERS=SECURE256:SECURE128:!AES-128-CBC:!ARCFOUR-128:!CAMELLIA-128-CBC:!3DES-CBC:!CAMELLIA-128-CBC
# Require and verify server certificate
#LDAP_TLS_CHECK_PEER=1
# Path to CA cert file. Used when server certificate verify is enabled
#LDAP_TLS_CACERT_FILE=/etc/ssl/certs/ca-certificates.crt
# Path to CA certs directory. Used when server certificate verify is enabled
#LDAP_TLS_CACERT_DIR=/etc/ssl/certs
# Wether to use starttls, implies LDAPv3 and requires ldap:// instead of ldaps://
# LDAP_START_TLS=1
#
# Security
#
# Set these to strong passwords to avoid intruders from impersonating a service account
# The service(s) won't start unless these are specified
# Running ./gen-passwords.sh will update .env with strong passwords
# You may skip the Jigasi and Jibri passwords if you are not using those
# DO NOT reuse passwords
#
# ENSURE YOU CHANGE THE BELOW!!!!!!!!
# XMPP password for Jicofo client connections
JICOFO_AUTH_PASSWORD=8fbfebe1142fac3efee956d7d65d0146
# XMPP password for JVB client connections
JVB_AUTH_PASSWORD=0a192f85b439288b54f520faacdf209d
# XMPP password for Jigasi MUC client connections
JIGASI_XMPP_PASSWORD=141b73ad8050cee2fe72c74ab1a86282
# XMPP recorder password for Jibri client connections
JIBRI_RECORDER_PASSWORD=45f6506538b556d3bfa4c04291fddad3
# XMPP password for Jibri client connections
JIBRI_XMPP_PASSWORD=7798d3b72dbffca6eb0e0ea384e816b7
#
# Docker Compose options
#
# Container restart policy
#RESTART_POLICY=unless-stopped
# Jitsi image version (useful for local development)
#JITSI_IMAGE_VERSION=latest

3
Video/Jitsi Meet/LICENSE Normal file
View File

@ -0,0 +1,3 @@
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work.

View File

@ -0,0 +1,31 @@
![Jitsi](./img/logo-Jitsi.png)
# Jitsi
# Installation
Pour utiliser Jitsi tout seul
```bash
docker compose up -d
```
Pour utiliser Jitsi avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
Pour utiliser Jitsi avec Nginx
```bash
docker compose -f docker-compose-nginx.yml up -d
```
# Utilisation
## Accueil
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,66 @@
![Fichier-Stockage](./img/logo-Fichier-Stockage.png)
URL : HHHHH
# Fichier-Stockage
GGGGG
# Téléchargement, Configuration et Lancement
## Téléchargement de Fichier-Stockage
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
## Modifier la configuration de Fichier-Stockage
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Fichier-Stockage
Pour utiliser Fichier-Stockage tout seul
```bash
docker compose up -d
```
Pour utiliser Fichier-Stockage avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Fichier-Stockage.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,400 @@
version: '3.5'
services:
# Frontend
web:
image: jitsi/web:${JITSI_IMAGE_VERSION:-stable-8922}
restart: ${RESTART_POLICY:-unless-stopped}
# remember to uncomment below if you do not plan on using a reverse proxy
# ports:
# - '${HTTP_PORT}:80'
# - '${HTTPS_PORT}:443'
volumes:
- ${CONFIG}/web:/config:Z
- ${CONFIG}/web/crontabs:/var/spool/cron/crontabs:Z
- ${CONFIG}/transcripts:/usr/share/jitsi-meet/transcripts:Z
environment:
- AMPLITUDE_ID
- ANALYTICS_SCRIPT_URLS
- ANALYTICS_WHITELISTED_EVENTS
- AUDIO_QUALITY_OPUS_BITRATE
- AUTO_CAPTION_ON_RECORD
- BRANDING_DATA_URL
- CALLSTATS_CUSTOM_SCRIPT_URL
- CALLSTATS_ID
- CALLSTATS_SECRET
- CHROME_EXTENSION_BANNER_JSON
- COLIBRI_WEBSOCKET_PORT
- CONFCODE_URL
- CONFIG_EXTERNAL_CONNECT
- DEFAULT_LANGUAGE
- DEPLOYMENTINFO_ENVIRONMENT
- DEPLOYMENTINFO_ENVIRONMENT_TYPE
- DEPLOYMENTINFO_REGION
- DEPLOYMENTINFO_SHARD
- DEPLOYMENTINFO_USERREGION
- DESKTOP_SHARING_FRAMERATE_MIN
- DESKTOP_SHARING_FRAMERATE_MAX
- DIALIN_NUMBERS_URL
- DIALOUT_AUTH_URL
- DIALOUT_CODES_URL
- DISABLE_AUDIO_LEVELS
- DISABLE_DEEP_LINKING
- DISABLE_GRANT_MODERATOR
- DISABLE_HTTPS
- DISABLE_KICKOUT
- DISABLE_LOCAL_RECORDING
- DISABLE_POLLS
- DISABLE_PRIVATE_CHAT
- DISABLE_PROFILE
- DISABLE_REACTIONS
- DISABLE_REMOTE_VIDEO_MENU
- DISABLE_START_FOR_ALL
- DROPBOX_APPKEY
- DROPBOX_REDIRECT_URI
- DYNAMIC_BRANDING_URL
- ENABLE_AUDIO_PROCESSING
- ENABLE_AUTH
- ENABLE_BREAKOUT_ROOMS
- ENABLE_CALENDAR
- ENABLE_COLIBRI_WEBSOCKET
- ENABLE_E2EPING
- ENABLE_FILE_RECORDING_SHARING
- ENABLE_GUESTS
- ENABLE_HSTS
- ENABLE_HTTP_REDIRECT
- ENABLE_IPV6
- ENABLE_LETSENCRYPT
- ENABLE_LIPSYNC
- ENABLE_NO_AUDIO_DETECTION
- ENABLE_NOISY_MIC_DETECTION
- ENABLE_OCTO
- ENABLE_OPUS_RED
- ENABLE_PREJOIN_PAGE
- ENABLE_P2P
- ENABLE_WELCOME_PAGE
- ENABLE_CLOSE_PAGE
- ENABLE_LIVESTREAMING
- ENABLE_LIVESTREAMING_DATA_PRIVACY_LINK
- ENABLE_LIVESTREAMING_HELP_LINK
- ENABLE_LIVESTREAMING_TERMS_LINK
- ENABLE_LIVESTREAMING_VALIDATOR_REGEXP_STRING
- ENABLE_LOCAL_RECORDING_NOTIFY_ALL_PARTICIPANT
- ENABLE_LOCAL_RECORDING_SELF_START
- ENABLE_RECORDING
- ENABLE_REMB
- ENABLE_REQUIRE_DISPLAY_NAME
- ENABLE_SERVICE_RECORDING
- ENABLE_SIMULCAST
- ENABLE_STATS_ID
- ENABLE_STEREO
- ENABLE_SUBDOMAINS
- ENABLE_TALK_WHILE_MUTED
- ENABLE_TCC
- ENABLE_TRANSCRIPTIONS
- ENABLE_XMPP_WEBSOCKET
- ENABLE_JAAS_COMPONENTS
- ETHERPAD_PUBLIC_URL
- ETHERPAD_URL_BASE
- E2EPING_NUM_REQUESTS
- E2EPING_MAX_CONFERENCE_SIZE
- E2EPING_MAX_MESSAGE_PER_SECOND
- GOOGLE_ANALYTICS_ID
- GOOGLE_API_APP_CLIENT_ID
- HIDE_PREMEETING_BUTTONS
- HIDE_PREJOIN_DISPLAY_NAME
- HIDE_PREJOIN_EXTRA_BUTTONS
- INVITE_SERVICE_URL
- LETSENCRYPT_DOMAIN
- LETSENCRYPT_EMAIL
- LETSENCRYPT_USE_STAGING
- MATOMO_ENDPOINT
- MATOMO_SITE_ID
- MICROSOFT_API_APP_CLIENT_ID
- NGINX_RESOLVER
- NGINX_WORKER_PROCESSES
- NGINX_WORKER_CONNECTIONS
- PEOPLE_SEARCH_URL
- PREFERRED_LANGUAGE
- PUBLIC_URL
- P2P_PREFERRED_CODEC
- RESOLUTION
- RESOLUTION_MIN
- RESOLUTION_WIDTH
- RESOLUTION_WIDTH_MIN
- START_AUDIO_MUTED
- START_AUDIO_ONLY
- START_BITRATE
- START_SILENT
- START_WITH_AUDIO_MUTED
- START_VIDEO_MUTED
- START_WITH_VIDEO_MUTED
- TESTING_CAP_SCREENSHARE_BITRATE
- TESTING_OCTO_PROBABILITY
- TOKEN_AUTH_URL
- TOOLBAR_BUTTONS
- TRANSLATION_LANGUAGES
- TRANSLATION_LANGUAGES_HEAD
- TZ
- USE_APP_LANGUAGE
- VIDEOQUALITY_BITRATE_H264_LOW
- VIDEOQUALITY_BITRATE_H264_STANDARD
- VIDEOQUALITY_BITRATE_H264_HIGH
- VIDEOQUALITY_BITRATE_VP8_LOW
- VIDEOQUALITY_BITRATE_VP8_STANDARD
- VIDEOQUALITY_BITRATE_VP8_HIGH
- VIDEOQUALITY_BITRATE_VP9_LOW
- VIDEOQUALITY_BITRATE_VP9_STANDARD
- VIDEOQUALITY_BITRATE_VP9_HIGH
- VIDEOQUALITY_ENFORCE_PREFERRED_CODEC
- VIDEOQUALITY_PREFERRED_CODEC
- XMPP_AUTH_DOMAIN
- XMPP_BOSH_URL_BASE
- XMPP_DOMAIN
- XMPP_GUEST_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_RECORDER_DOMAIN
- XMPP_PORT
- WHITEBOARD_ENABLED
- WHITEBOARD_COLLAB_SERVER_PUBLIC_URL
networks:
meet.jitsi:
proxy: # remove if you're not using a reverse proxy (including labels below)
labels:
- "traefik.enable=true"
- "traefik.http.routers.jitsi.entrypoints=http"
- "traefik.http.routers.jitsi.rule=Host(`meet2.jimsgarage.co.uk`)"
- "traefik.http.middlewares.jitsi-https-redirect.redirectscheme.scheme=https"
- "traefik.http.routers.jitsi.middlewares=jitsi-https-redirect"
- "traefik.http.routers.jitsi-secure.entrypoints=https"
- "traefik.http.routers.jitsi-secure.rule=Host(`meet2.jimsgarage.co.uk`)"
- "traefik.http.routers.jitsi-secure.tls=true"
- "traefik.http.routers.jitsi-secure.service=jitsi"
- "traefik.http.services.jitsi.loadbalancer.server.port=80"
- "traefik.docker.network=proxy"
# XMPP server
prosody:
image: jitsi/prosody:${JITSI_IMAGE_VERSION:-stable-8922}
restart: ${RESTART_POLICY:-unless-stopped}
expose:
- '${XMPP_PORT:-5222}'
- '5347'
- '5280'
volumes:
- ${CONFIG}/prosody/config:/config:Z
- ${CONFIG}/prosody/prosody-plugins-custom:/prosody-plugins-custom:Z
environment:
- AUTH_TYPE
- DISABLE_POLLS
- ENABLE_AUTH
- ENABLE_AV_MODERATION
- ENABLE_BREAKOUT_ROOMS
- ENABLE_END_CONFERENCE
- ENABLE_GUESTS
- ENABLE_IPV6
- ENABLE_LOBBY
- ENABLE_RECORDING
- ENABLE_XMPP_WEBSOCKET
- ENABLE_JAAS_COMPONENTS
- GC_TYPE
- GC_INC_TH
- GC_INC_SPEED
- GC_INC_STEP_SIZE
- GC_GEN_MIN_TH
- GC_GEN_MAX_TH
- GLOBAL_CONFIG
- GLOBAL_MODULES
- JIBRI_RECORDER_USER
- JIBRI_RECORDER_PASSWORD
- JIBRI_XMPP_USER
- JIBRI_XMPP_PASSWORD
- JICOFO_AUTH_PASSWORD
- JICOFO_COMPONENT_SECRET
- JIGASI_XMPP_USER
- JIGASI_XMPP_PASSWORD
- JVB_AUTH_USER
- JVB_AUTH_PASSWORD
- JWT_APP_ID
- JWT_APP_SECRET
- JWT_ACCEPTED_ISSUERS
- JWT_ACCEPTED_AUDIENCES
- JWT_ASAP_KEYSERVER
- JWT_ALLOW_EMPTY
- JWT_AUTH_TYPE
- JWT_ENABLE_DOMAIN_VERIFICATION
- JWT_TOKEN_AUTH_MODULE
- MATRIX_UVS_URL
- MATRIX_UVS_ISSUER
- MATRIX_UVS_AUTH_TOKEN
- MATRIX_UVS_SYNC_POWER_LEVELS
- LOG_LEVEL
- LDAP_AUTH_METHOD
- LDAP_BASE
- LDAP_BINDDN
- LDAP_BINDPW
- LDAP_FILTER
- LDAP_VERSION
- LDAP_TLS_CIPHERS
- LDAP_TLS_CHECK_PEER
- LDAP_TLS_CACERT_FILE
- LDAP_TLS_CACERT_DIR
- LDAP_START_TLS
- LDAP_URL
- LDAP_USE_TLS
- MAX_PARTICIPANTS
- PROSODY_AUTH_TYPE
- PROSODY_RESERVATION_ENABLED
- PROSODY_RESERVATION_REST_BASE_URL
- PROSODY_ENABLE_RATE_LIMITS
- PROSODY_RATE_LIMIT_LOGIN_RATE
- PROSODY_RATE_LIMIT_SESSION_RATE
- PROSODY_RATE_LIMIT_TIMEOUT
- PROSODY_RATE_LIMIT_ALLOW_RANGES
- PROSODY_RATE_LIMIT_CACHE_SIZE
- PUBLIC_URL
- TURN_CREDENTIALS
- TURN_HOST
- TURNS_HOST
- TURN_PORT
- TURNS_PORT
- TURN_TRANSPORT
- TZ
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_GUEST_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_MODULES
- XMPP_MUC_MODULES
- XMPP_MUC_CONFIGURATION
- XMPP_INTERNAL_MUC_MODULES
- XMPP_RECORDER_DOMAIN
- XMPP_PORT
networks:
meet.jitsi:
aliases:
- ${XMPP_SERVER:-xmpp.meet.jitsi}
# Focus component
jicofo:
image: jitsi/jicofo:${JITSI_IMAGE_VERSION:-stable-8922}
restart: ${RESTART_POLICY:-unless-stopped}
ports:
- '127.0.0.1:${JICOFO_REST_PORT:-8888}:8888'
volumes:
- ${CONFIG}/jicofo:/config:Z
environment:
- AUTH_TYPE
- BRIDGE_AVG_PARTICIPANT_STRESS
- BRIDGE_STRESS_THRESHOLD
- ENABLE_AUTH
- ENABLE_AUTO_OWNER
- ENABLE_CODEC_VP8
- ENABLE_CODEC_VP9
- ENABLE_CODEC_H264
- ENABLE_CODEC_OPUS_RED
- ENABLE_JVB_XMPP_SERVER
- ENABLE_OCTO
- ENABLE_RECORDING
- ENABLE_SCTP
- ENABLE_AUTO_LOGIN
- JICOFO_AUTH_LIFETIME
- JICOFO_AUTH_PASSWORD
- JICOFO_AUTH_TYPE
- JICOFO_BRIDGE_REGION_GROUPS
- JICOFO_ENABLE_AUTH
- JICOFO_ENABLE_BRIDGE_HEALTH_CHECKS
- JICOFO_CONF_INITIAL_PARTICIPANT_WAIT_TIMEOUT
- JICOFO_CONF_SINGLE_PARTICIPANT_TIMEOUT
- JICOFO_CONF_SOURCE_SIGNALING_DELAYS
- JICOFO_CONF_MAX_AUDIO_SENDERS
- JICOFO_CONF_MAX_VIDEO_SENDERS
- JICOFO_CONF_STRIP_SIMULCAST
- JICOFO_CONF_SSRC_REWRITING
- JICOFO_ENABLE_HEALTH_CHECKS
- JICOFO_ENABLE_REST
- JICOFO_HEALTH_CHECKS_USE_PRESENCE
- JICOFO_MULTI_STREAM_BACKWARD_COMPAT
- JICOFO_OCTO_REGION
- JIBRI_BREWERY_MUC
- JIBRI_REQUEST_RETRIES
- JIBRI_PENDING_TIMEOUT
- JIGASI_BREWERY_MUC
- JIGASI_SIP_URI
- JVB_BREWERY_MUC
- JVB_XMPP_AUTH_DOMAIN
- JVB_XMPP_INTERNAL_MUC_DOMAIN
- JVB_XMPP_PORT
- JVB_XMPP_SERVER
- MAX_BRIDGE_PARTICIPANTS
- OCTO_BRIDGE_SELECTION_STRATEGY
- SENTRY_DSN="${JICOFO_SENTRY_DSN:-0}"
- SENTRY_ENVIRONMENT
- SENTRY_RELEASE
- TZ
- XMPP_DOMAIN
- XMPP_AUTH_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_MUC_DOMAIN
- XMPP_RECORDER_DOMAIN
- XMPP_SERVER
- XMPP_PORT
depends_on:
- prosody
networks:
meet.jitsi:
# Video bridge
jvb:
image: jitsi/jvb:${JITSI_IMAGE_VERSION:-stable-8922}
restart: ${RESTART_POLICY:-unless-stopped}
ports:
- '${JVB_PORT:-10000}:${JVB_PORT:-10000}/udp'
- '127.0.0.1:${JVB_COLIBRI_PORT:-8080}:8080'
volumes:
- ${CONFIG}/jvb:/config:Z
environment:
- DOCKER_HOST_ADDRESS
- ENABLE_COLIBRI_WEBSOCKET
- ENABLE_JVB_XMPP_SERVER
- ENABLE_OCTO
- JVB_ADVERTISE_IPS
- JVB_ADVERTISE_PRIVATE_CANDIDATES
- JVB_AUTH_USER
- JVB_AUTH_PASSWORD
- JVB_BREWERY_MUC
- JVB_DISABLE_STUN
- JVB_PORT
- JVB_MUC_NICKNAME
- JVB_STUN_SERVERS
- JVB_OCTO_BIND_ADDRESS
- JVB_OCTO_REGION
- JVB_OCTO_RELAY_ID
- JVB_WS_DOMAIN
- JVB_WS_SERVER_ID
- JVB_XMPP_AUTH_DOMAIN
- JVB_XMPP_INTERNAL_MUC_DOMAIN
- JVB_XMPP_PORT
- JVB_XMPP_SERVER
- PUBLIC_URL
- SENTRY_DSN="${JVB_SENTRY_DSN:-0}"
- SENTRY_ENVIRONMENT
- SENTRY_RELEASE
- COLIBRI_REST_ENABLED
- SHUTDOWN_REST_ENABLED
- TZ
- XMPP_AUTH_DOMAIN
- XMPP_INTERNAL_MUC_DOMAIN
- XMPP_SERVER
- XMPP_PORT
depends_on:
- prosody
networks:
meet.jitsi:
# Custom network so all services can communicate using a FQDN
networks:
meet.jitsi:
proxy: # remove if you're not using a proxy
external: true

View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
function generatePassword() {
openssl rand -hex 16
}
JICOFO_AUTH_PASSWORD=$(generatePassword)
JVB_AUTH_PASSWORD=$(generatePassword)
JIGASI_XMPP_PASSWORD=$(generatePassword)
JIBRI_RECORDER_PASSWORD=$(generatePassword)
JIBRI_XMPP_PASSWORD=$(generatePassword)
sed -i.bak \
-e "s#JICOFO_AUTH_PASSWORD=.*#JICOFO_AUTH_PASSWORD=${JICOFO_AUTH_PASSWORD}#g" \
-e "s#JVB_AUTH_PASSWORD=.*#JVB_AUTH_PASSWORD=${JVB_AUTH_PASSWORD}#g" \
-e "s#JIGASI_XMPP_PASSWORD=.*#JIGASI_XMPP_PASSWORD=${JIGASI_XMPP_PASSWORD}#g" \
-e "s#JIBRI_RECORDER_PASSWORD=.*#JIBRI_RECORDER_PASSWORD=${JIBRI_RECORDER_PASSWORD}#g" \
-e "s#JIBRI_XMPP_PASSWORD=.*#JIBRI_XMPP_PASSWORD=${JIBRI_XMPP_PASSWORD}#g" \
"$(dirname "$0")/.env"

3
Video/MeTube/LICENSE Normal file
View File

@ -0,0 +1,3 @@
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work.

66
Video/MeTube/README.md Normal file
View File

@ -0,0 +1,66 @@
![MeTube](./img/logo-MeTube.png)
URL :
# MeTube
# Téléchargement, Configuration et Lancement
## Téléchargement de MeTube
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\MeTube
```
## Modifier la configuration de MeTube
Saisir la commande pour vous rendre dans le dossier
```bash
cd Video\MeTube
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de MeTube
Pour utiliser MeTube tout seul
```bash
docker compose up -d
```
Pour utiliser MeTube avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://MeTube.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,66 @@
![Fichier-Stockage](./img/logo-Fichier-Stockage.png)
URL : HHHHH
# Fichier-Stockage
GGGGG
# Téléchargement, Configuration et Lancement
## Téléchargement de Fichier-Stockage
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
## Modifier la configuration de Fichier-Stockage
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Fichier-Stockage
Pour utiliser Fichier-Stockage tout seul
```bash
docker compose up -d
```
Pour utiliser Fichier-Stockage avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Fichier-Stockage.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,8 @@
#### NETWORKS
networks:
back_network:
driver: bridge
attachable: true
#### SERVICES
services:

View File

@ -0,0 +1 @@
.github

View File

@ -0,0 +1,7 @@
module.exports = {
semi: true,
trailingComma: 'all',
singleQuote: true,
printWidth: 120,
tabWidth: 4,
};

View File

@ -0,0 +1,109 @@
# MiroTalk Open Source Community Code of Conduct
## About the Code of Conduct
Equality is a core value at MiroTalk. We believe a diverse and inclusive
community fosters innovation and creativity, and are committed to building a
culture where everyone feels included.
MiroTalk open-source projects are committed to providing a friendly, safe, and
welcoming environment for all, regardless of gender identity and expression,
sexual orientation, disability, physical appearance, body size, ethnicity, nationality,
race, age, religion, level of experience, education, socioeconomic status, or
other similar personal characteristics.
The goal of this code of conduct is to specify a baseline standard of behavior so
that people with different social values and communication styles can work
together effectively, productively, and respectfully in our open source community.
It also establishes a mechanism for reporting issues and resolving conflicts.
All questions and reports of abusive, harassing, or otherwise unacceptable behavior
in a MiroTalk open-source project may be reported by contacting the MiroTalk
Open Source Conduct Committee at ossconduct.mirotalk@gmail.com.
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of gender
identity and expression, sexual orientation, disability, physical appearance,
body size, ethnicity, nationality, race, age, religion, level of experience, education,
socioeconomic status, or other similar personal characteristics.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
- Using welcoming and inclusive language
- Being respectful of differing viewpoints and experiences
- Gracefully accepting constructive criticism
- Focusing on what is best for the community
- Showing empathy toward other community members
Examples of unacceptable behavior by participants include:
- The use of sexualized language or imagery and unwelcome sexual attention or
advances
- Personal attacks, insulting/derogatory comments, or trolling
- Public or private harassment
- Publishing, or threatening to publish, others' private information—such as
a physical or electronic address—without explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
- Advocating for or encouraging any of the above behaviors
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned with this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project email
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the MiroTalk Open Source Conduct Committee
at ossconduct.mirotalk@gmail.com. All complaints will be reviewed and investigated
and will result in a response that is deemed necessary and appropriate to the
circumstances. The committee is obligated to maintain confidentiality with
regard to the reporter of an incident. Further details of specific enforcement
policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership and the MiroTalk Open Source Conduct
Committee.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][contributor-covenant-home],
version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code_of_conduct.html][v1.4].
It includes adaptions and additions from [Go Community Code of Conduct][golang-coc],
[CNCF Code of Conduct][cncf-coc], and [Microsoft Open Source Code of Conduct][microsoft-coc].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[contributor-covenant-home]: https://www.contributor-covenant.org
[v1.4]: https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[golang-coc]: https://golang.org/conduct
[cncf-coc]: https://github.com/cncf/foundation/blob/master/code-of-conduct.md
[microsoft-coc]: https://opensource.microsoft.com/codeofconduct/
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -0,0 +1,22 @@
FROM node:18-slim
WORKDIR /src
# https://mediasoup.org/documentation/v3/mediasoup/installation/
ENV MEDIASOUP_SKIP_WORKER_PREBUILT_DOWNLOAD="true"
COPY package.json .
RUN \
DEBIAN_FRONTEND=noninteractive apt-get update && \
apt-get install -y --no-install-recommends build-essential python3-pip && \
npm install && \
apt-get -y purge --auto-remove build-essential python3-pip && \
apt-get install -y --no-install-recommends python3 && \
npm cache clean --force && \
rm -rf /tmp/* /var/lib/apt/lists/* /var/tmp/* /usr/share/doc/*
COPY app app
COPY public public
CMD npm start

View File

@ -0,0 +1,3 @@
Additional permission under GNU GPL version 3 section 7
If you modify this Program, or any covered work, by linking or combining it with [name of library] (or a modified version of that library), containing parts covered by the terms of [name of library's license], the licensors of this Program grant you additional permission to convey the resulting work. Corresponding Source for a non-source form of such a combination shall include the source code for the parts of [name of library] used as well as that of the covered work.

View File

@ -0,0 +1,32 @@
![adminer](./img/logo-Adminer.png)
# Adminer
Adminer est un outil de gestion de base de données complet écrit en PHP. Inversement à phpMyAdmin, il consiste en un seul fichier prêt à être déployé sur le serveur cible. Adminer est disponible pour MySQL, MariaDB, PostgreSQL, SQLite, MS SQL, Oracle, Elasticsearch, MongoDB et autres via plugin
# Installation
Pour utiliser Adminer tout seul
```bash
docker compose up -d
```
Pour utiliser Adminer avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
Pour utiliser Adminer avec Nginx
```bash
docker compose -f docker-compose-nginx.yml up -d
```
# Utilisation
## Accueil
![adminer-accueil](./img/Adminer-000.png)
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,33 @@
# Security Policy
## Reporting a Vulnerability
Security is very important to us.
If you have discovered a security issue, please contact miroslav.pejic.85@gmail.com directly. Please refrain from directly creating a GitHub issue and publicly disclosing the vulnerability.
We prefer a Coordinated Vulnerability Disclosure (CVD) to properly understand and fix the root cause problem.
Your report should include:
- Product version ([GitHub](https://github.com/miroslavpejic85/mirotalksfu/commits/main) commit hash or [DockerHub](https://hub.docker.com/r/mirotalk/sfu) sha256 digest hash)
- The affected component if possible (RoomClient.js, Server.js, etc.)
- A vulnerability description
- Reproduction steps
A member of the security team will confirm the vulnerability, determine its impact, and develop a fix.
The fix will be applied to the master branch, tested, and packaged in the next security release.
Thanks in advance for your support to make our products safer!
---
## 🙏 Acknowledgments
We would like to extend our gratitude to the following individuals for their responsible disclosure of security vulnerabilities:
| Name | Contact |
| ----------------- | ---------------------- |
| `Hendrik Siewert` | hendrik.siewert@upb.de |
| `Caio Fook` | caio.fook@gmail.com |
Their dedication to security has contributed to the continuous improvement of our systems, ensuring the safety and privacy of our users and data.

View File

@ -0,0 +1,62 @@
![restAPI](restAPI.png)
## Create a meeting
Create a meeting with a `HTTP request` containing the `API_KEY` sent to MiroTalks server. The response contains a `meeting` URL that can be `embedded` in your client within an `iframe`.
The `API_KEY` is defined in the `app/src/config.js`, change it with your own.
```js
api: {
// app/api
keySecret: 'mirotalksfu_default_secret',
}
```
Some examples demonstrating how to call the API:
```bash
# js
node meetings.js
node meeting.js
node join.js
# php
php meetings.php
php meeting.php
php join.php
# python
python3 meetings.py
python3 meeting.py
python3 join.py
# bash
./meetings.sh
./meeting.sh
./join.sh
```
## Embed a meeting
Embedding a meeting into a `service` or `app` requires using an `iframe` with the `src` attribute specified as the `meeting` from `HTTP response`.
```html
<iframe
allow="camera; microphone; display-capture; fullscreen; clipboard-read; clipboard-write; autoplay"
src="https://sfu.mirotalk.com/join/your_room_name"
style="height: 100vh; width: 100vw; border: 0px;"
></iframe>
```
## Fast Integration
Develop your `website` or `application`, and bring `video meetings` in with a simple `iframe`.
```html
<iframe
allow="camera; microphone; display-capture; fullscreen; clipboard-read; clipboard-write; autoplay"
src="https://sfu.mirotalk.com/newroom"
style="height: 100vh; width: 100vw; border: 0px;"
></iframe>
```

View File

@ -0,0 +1,46 @@
'use strict';
async function getJoin() {
try {
// Use dynamic import with await
const { default: fetch } = await import('node-fetch');
const API_KEY_SECRET = 'mirotalksfu_default_secret';
const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/join';
//const MIROTALK_URL = 'http://localhost:3010/api/v1/join';
const response = await fetch(MIROTALK_URL, {
method: 'POST',
headers: {
authorization: API_KEY_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({
room: 'test',
roomPassword: false,
name: 'mirotalksfu',
audio: true,
video: true,
screen: true,
hide: false,
notify: true,
token: {
username: 'username',
password: 'password',
presenter: true,
expire: '1h',
},
}),
});
const data = await response.json();
if (data.error) {
console.log('Error:', data.error);
} else {
console.log('join:', data.join);
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
getJoin();

View File

@ -0,0 +1,45 @@
<?php
$API_KEY_SECRET = "mirotalksfu_default_secret";
$MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/join";
// $MIROTALK_URL = "http://localhost:3010/api/v1/join";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MIROTALK_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = [
'authorization:' . $API_KEY_SECRET,
'Content-Type: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$data = array(
"room" => "test",
"roomPassword" => false,
"name" => "mirotalksfu",
"audio" => true,
"video" => true,
"screen" => true,
"hide" => false,
"notify" => true,
"token" => array(
"username" => "username",
"password" => "password",
"presenter" => true,
"expire" => "1h",
),
);
$data_string = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status code: $httpcode \n";
$data = json_decode($response);
echo "join: ", $data->{'join'}, "\n";

View File

@ -0,0 +1,39 @@
# pip3 install requests
import requests
import json
API_KEY_SECRET = "mirotalksfu_default_secret"
MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/join"
# MIROTALK_URL = "http://localhost:3010/api/v1/join"
headers = {
"authorization": API_KEY_SECRET,
"Content-Type": "application/json",
}
data = {
"room": "test",
"roomPassword": "false",
"name": "mirotalksfu",
"audio": "true",
"video": "true",
"screen": "true",
"hide": "false",
"notify": "true",
"token": {
"username": "username",
"password": "password",
"presenter": "true",
"expire": "1h",
}
}
response = requests.post(
MIROTALK_URL,
headers=headers,
json=data,
)
print("Status code:", response.status_code)
data = json.loads(response.text)
print("join:", data["join"])

View File

@ -0,0 +1,11 @@
#!/bin/bash
API_KEY_SECRET="mirotalksfu_default_secret"
MIROTALK_URL="https://sfu.mirotalk.com/api/v1/join"
# MIROTALK_URL="http://localhost:3010/api/v1/join"
curl $MIROTALK_URL \
--header "authorization: $API_KEY_SECRET" \
--header "Content-Type: application/json" \
--data '{"room":"test","roomPassword":"false","name":"mirotalksfu","audio":"true","video":"true","screen":"false","hide":"false","notify":"true","token":{"username":"username","password":"password","presenter":"true", "expire":"1h"}}' \
--request POST

View File

@ -0,0 +1,30 @@
'use strict';
async function getMeeting() {
try {
// Use dynamic import with await
const { default: fetch } = await import('node-fetch');
const API_KEY_SECRET = 'mirotalksfu_default_secret';
const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/meeting';
// const MIROTALK_URL = 'http://localhost:3010/api/v1/meeting';
const response = await fetch(MIROTALK_URL, {
method: 'POST',
headers: {
authorization: API_KEY_SECRET,
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (data.error) {
console.log('Error:', data.error);
} else {
console.log('meeting:', data.meeting);
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
getMeeting();

View File

@ -0,0 +1,25 @@
<?php
$API_KEY_SECRET = "mirotalksfu_default_secret";
$MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/meeting";
// $MIROTALK_URL = "http://localhost:3010/api/v1/meeting";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MIROTALK_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = [
'authorization:' . $API_KEY_SECRET,
'Content-Type: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status code: $httpcode \n";
$data = json_decode($response);
echo "meeting: ", $data->{'meeting'}, "\n";

View File

@ -0,0 +1,21 @@
# pip3 install requests
import requests
import json
API_KEY_SECRET = "mirotalksfu_default_secret"
MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/meeting"
# MIROTALK_URL = "http://localhost:3010/api/v1/meeting"
headers = {
"authorization": API_KEY_SECRET,
"Content-Type": "application/json",
}
response = requests.post(
MIROTALK_URL,
headers=headers
)
print("Status code:", response.status_code)
data = json.loads(response.text)
print("meeting:", data["meeting"])

View File

@ -0,0 +1,10 @@
#!/bin/bash
API_KEY_SECRET="mirotalksfu_default_secret"
MIROTALK_URL="https://sfu.mirotalk.com/api/v1/meeting"
# MIROTALK_URL="http://localhost:3010/api/v1/meeting"
curl $MIROTALK_URL \
--header "authorization: $API_KEY_SECRET" \
--header "Content-Type: application/json" \
--request POST

View File

@ -0,0 +1,34 @@
'use strict';
async function getMeetings() {
try {
// Use dynamic import with await
const { default: fetch } = await import('node-fetch');
const API_KEY_SECRET = 'mirotalksfu_default_secret';
const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/meetings';
//const MIROTALK_URL = 'http://localhost:3010/api/v1/meetings';
const response = await fetch(MIROTALK_URL, {
method: 'GET',
headers: {
authorization: API_KEY_SECRET,
'Content-Type': 'application/json',
},
});
const data = await response.json();
if (data.error) {
console.log('Error:', data.error);
} else {
if (data && data.meetings) {
const meetings = data.meetings;
const formattedData = JSON.stringify({ meetings }, null, 2);
console.log(formattedData);
}
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
getMeetings();

View File

@ -0,0 +1,29 @@
<?php
$API_KEY_SECRET = "mirotalksfu_default_secret";
$MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/meetings";
//$MIROTALK_URL = "http://localhost:3010/api/v1/meetings";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MIROTALK_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPGET, true);
$headers = [
'authorization:' . $API_KEY_SECRET,
'Content-Type: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status code: $httpcode \n";
if ($response) {
echo json_encode(json_decode($response), JSON_PRETTY_PRINT);
} else {
echo "Failed to retrieve data.\n";
}

View File

@ -0,0 +1,26 @@
# pip3 install requests
import requests
import json
API_KEY_SECRET = "mirotalksfu_default_secret"
MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/meetings"
#MIROTALK_URL = "http://localhost:3010/api/v1/meetings"
headers = {
"authorization": API_KEY_SECRET,
"Content-Type": "application/json",
}
response = requests.get(
MIROTALK_URL,
headers=headers
)
print("Status code:", response.status_code)
if response.status_code == 200:
data = response.json()
pretty_printed_data = json.dumps(data, indent=4)
print(data)
else:
print("Failed to retrieve data. Error:", response.text)

View File

@ -0,0 +1,10 @@
#!/bin/bash
API_KEY_SECRET="mirotalksfu_default_secret"
MIROTALK_URL="https://sfu.mirotalk.com/api/v1/meetings"
#MIROTALK_URL="http://localhost:3010/api/v1/meetings"
curl $MIROTALK_URL \
--header "authorization: $API_KEY_SECRET" \
--header "Content-Type: application/json" \
--request GET

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -0,0 +1,204 @@
swagger: '2.0'
info:
title: MiroTalk SFU API
description: API description for external applications that integrate with MiroTalk SFU.
version: 1.0.1
basePath: /api/v1
schemes:
- https
- http
paths:
/meetings:
get:
tags:
- 'meetings'
summary: 'Get meetings'
description: 'Get meetings'
produces:
- 'application/json'
security:
- secretApiKey: []
responses:
'200':
description: 'Get Meetings done'
schema:
$ref: '#/definitions/MeetingsResponse'
'403':
description: 'Unauthorized!'
/meeting:
post:
tags:
- 'meeting'
summary: 'Create meeting'
description: 'Create meeting'
consumes:
- 'application/json'
produces:
- 'application/json'
security:
- secretApiKey: []
responses:
'200':
description: 'Meeting created'
schema:
$ref: '#/definitions/MeetingResponse'
'403':
description: 'Unauthorized!'
/join:
post:
tags:
- 'join'
summary: 'Create direct join'
description: 'Create join'
parameters:
- in: body
name: Join
description: Custom Join URL.
schema:
$ref: '#/definitions/JoinRequest'
consumes:
- 'application/json'
produces:
- 'application/json'
security:
- secretApiKey: []
responses:
'200':
description: 'Direct join created'
schema:
$ref: '#/definitions/JoinResponse'
'403':
description: 'Unauthorized!'
/token:
post:
tags:
- 'token'
summary: 'Get token'
description: 'Get token'
parameters:
- in: body
name: token
description: Custom Token.
schema:
$ref: '#/definitions/TokenRequest'
consumes:
- 'application/json'
produces:
- 'application/json'
security:
- secretApiKey: []
responses:
'200':
description: 'Get token done'
schema:
$ref: '#/definitions/TokenResponse'
'403':
description: 'Unauthorized!'
securityDefinitions:
secretApiKey:
type: 'apiKey'
name: 'authorization'
in: 'header'
description: 'Format like this: authorization: {API_KEY_SECRET}'
definitions:
MeetingsResponse:
type: object
properties:
meetings:
type: array
items:
$ref: '#/definitions/Meeting'
MeetingResponse:
type: 'object'
properties:
meeting:
type: string
JoinRequest:
type: object
properties:
room:
type: string
default: 'test'
roomPassword:
type: ['boolean', 'string'] # Allow boolean or string type
default: false
name:
type: string
default: 'mirotalksfu'
audio:
type: boolean
default: false
video:
type: boolean
default: false
screen:
type: boolean
default: false
hide:
type: boolean
default: false
notify:
type: boolean
default: false
token:
$ref: '#/definitions/TokenRequest'
TokenRequest:
type: object
properties:
username:
type: string
default: 'username'
password:
type: string
default: 'password'
presenter:
type: boolean
default: true
expire:
type: string
default: '1h'
JoinResponse:
type: 'object'
properties:
join:
type: string
TokenResponse:
type: 'object'
properties:
token:
type: string
Peer:
type: object
properties:
name:
type: string
presenter:
type: boolean
video:
type: boolean
audio:
type: boolean
screen:
type: boolean
hand:
type: boolean
os:
type: string
browser:
type: string
Meeting:
type: object
properties:
roomId:
type: string
peers:
type: array
items:
$ref: '#/definitions/Peer'

View File

@ -0,0 +1,36 @@
'use strict';
async function getToken() {
try {
// Use dynamic import with await
const { default: fetch } = await import('node-fetch');
const API_KEY_SECRET = 'mirotalksfu_default_secret';
const MIROTALK_URL = 'https://sfu.mirotalk.com/api/v1/token';
//const MIROTALK_URL = 'http://localhost:3010/api/v1/token';
const response = await fetch(MIROTALK_URL, {
method: 'POST',
headers: {
authorization: API_KEY_SECRET,
'Content-Type': 'application/json',
},
body: JSON.stringify({
username: 'username',
password: 'password',
presenter: true,
expire: '1h',
}),
});
const data = await response.json();
if (data.error) {
console.log('Error:', data.error);
} else {
console.log('token:', data.token);
}
} catch (error) {
console.error('Error fetching data:', error);
}
}
getToken();

View File

@ -0,0 +1,37 @@
<?php
$API_KEY_SECRET = "mirotalksfu_default_secret";
$MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/token";
#$MIROTALK_URL = "http://localhost:3010/api/v1/token";
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $MIROTALK_URL);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = [
'authorization:' . $API_KEY_SECRET,
'Content-Type: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$data = array(
"username" => "username",
"password" => "password",
"presenter" => true,
"expire" => "1h",
);
$data_string = json_encode($data);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);
$response = curl_exec($ch);
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
echo "Status code: $httpcode \n";
$data = json_decode($response);
echo "token: ", $data->{'token'}, "\n";

View File

@ -0,0 +1,29 @@
# pip3 install requests
import requests
import json
API_KEY_SECRET = "mirotalksfu_default_secret"
MIROTALK_URL = "https://sfu.mirotalk.com/api/v1/token"
#MIROTALK_URL = "http://localhost:3010/api/v1/token"
headers = {
"authorization": API_KEY_SECRET,
"Content-Type": "application/json",
}
data = {
"username": "username",
"password": "password",
"presenter": "true",
"expire": "1h"
}
response = requests.post(
MIROTALK_URL,
headers=headers,
json=data
)
print("Status code:", response.status_code)
data = json.loads(response.text)
print("token:", data["token"])

View File

@ -0,0 +1,11 @@
#!/bin/bash
API_KEY_SECRET="mirotalksfu_default_secret"
MIROTALK_URL="https://sfu.mirotalk.com/api/v1/token"
#MIROTALK_URL="http://localhost:3010/api/v1/token"
curl $MIROTALK_URL \
--header "authorization: $API_KEY_SECRET" \
--header "Content-Type: application/json" \
--data '{"username":"username","password":"password","presenter":"true", "expire":"1h"}' \
--request POST

View File

@ -0,0 +1,42 @@
'use strict';
module.exports = class Host {
constructor() {
this.authorizedIPs = new Map();
}
/**
* Get authorized IPs
* @returns object
*/
getAuthorizedIPs() {
return Object.fromEntries(this.authorizedIPs);
}
/**
* Set authorized IP
* @param {string} ip
* @param {boolean} authorized
*/
setAuthorizedIP(ip, authorized) {
this.authorizedIPs.set(ip, authorized);
}
/**
* Check if IP is authorized
* @param {string} ip
* @returns boolean
*/
isAuthorizedIP(ip) {
return this.authorizedIPs.has(ip);
}
/**
* Delete ip from authorized IPs
* @param {string} ip
* @returns boolean
*/
deleteIP(ip) {
return this.authorizedIPs.delete(ip);
}
};

View File

@ -0,0 +1,90 @@
'use strict';
const util = require('util');
const colors = require('colors');
const config = require('./config');
config.console.colors ? colors.enable() : colors.disable();
const options = {
depth: null,
colors: true,
};
module.exports = class Logger {
constructor(appName = 'miroTalkSfu') {
this.appName = colors.yellow(appName);
this.debugOn = config.console.debug;
this.timeStart = Date.now();
this.timeEnd = null;
this.timeElapsedMs = null;
this.tzOptions = {
timeZone: process.env.TZ || config.console.timeZone || 'UTC',
hour12: false,
};
}
debug(msg, op = '') {
if (this.debugOn) {
this.timeEnd = Date.now();
this.timeElapsedMs = this.getFormatTime(Math.floor(this.timeEnd - this.timeStart));
console.debug(
'[' + this.getDateTime() + '] [' + this.appName + '] ' + msg,
util.inspect(op, options),
this.timeElapsedMs,
);
this.timeStart = Date.now();
}
}
log(msg, op = '') {
console.log('[' + this.getDateTime() + '] [' + this.appName + '] ' + msg, util.inspect(op, options));
}
info(msg, op = '') {
console.info(
'[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.green(msg),
util.inspect(op, options),
);
}
warn(msg, op = '') {
console.warn(
'[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.yellow(msg),
util.inspect(op, options),
);
}
error(msg, op = '') {
console.error(
'[' + this.getDateTime() + '] [' + this.appName + '] ' + colors.red(msg),
util.inspect(op, options),
);
}
getDateTime() {
const currentTime = new Date().toLocaleString('en-US', this.tzOptions);
const milliseconds = String(new Date().getMilliseconds()).padStart(3, '0');
return colors.cyan(`${currentTime}:${milliseconds}`);
}
getFormatTime(ms) {
let time = Math.floor(ms);
let type = 'ms';
if (ms >= 1000) {
time = Math.floor((ms / 1000) % 60);
type = 's';
}
if (ms >= 60000) {
time = Math.floor((ms / 1000 / 60) % 60);
type = 'm';
}
if (ms >= (3, 6e6)) {
time = Math.floor((ms / 1000 / 60 / 60) % 24);
type = 'h';
}
return colors.magenta('+' + time + type);
}
};

View File

@ -0,0 +1,316 @@
'use strict';
const Logger = require('./Logger');
const log = new Logger('Peer');
module.exports = class Peer {
constructor(socket_id, data) {
const { peer_info } = data;
const { peer_name, peer_presenter, peer_audio, peer_video, peer_video_privacy, peer_recording, peer_hand } =
peer_info;
this.id = socket_id;
this.peer_info = peer_info;
this.peer_name = peer_name;
this.peer_presenter = peer_presenter;
this.peer_audio = peer_audio;
this.peer_video = peer_video;
this.peer_video_privacy = peer_video_privacy;
this.peer_recording = peer_recording;
this.peer_hand = peer_hand;
this.transports = new Map();
this.consumers = new Map();
this.producers = new Map();
}
// ####################################################
// UPDATE PEER INFO
// ####################################################
updatePeerInfo(data) {
log.debug('Update peer info', data);
switch (data.type) {
case 'audio':
case 'audioType':
this.peer_info.peer_audio = data.status;
this.peer_audio = data.status;
break;
case 'video':
case 'videoType':
this.peer_info.peer_video = data.status;
this.peer_video = data.status;
if (data.status == false) {
this.peer_info.peer_video_privacy = data.status;
this.peer_video_privacy = data.status;
}
break;
case 'screen':
case 'screenType':
this.peer_info.peer_screen = data.status;
break;
case 'hand':
this.peer_info.peer_hand = data.status;
this.peer_hand = data.status;
break;
case 'privacy':
this.peer_info.peer_video_privacy = data.status;
this.peer_video_privacy = data.status;
break;
case 'presenter':
this.peer_info.peer_presenter = data.status;
this.peer_presenter = data.status;
break;
case 'recording':
this.peer_info.peer_recording = data.status;
this.peer_recording = data.status;
break;
default:
break;
}
}
// ####################################################
// TRANSPORT
// ####################################################
getTransports() {
return JSON.parse(JSON.stringify([...this.transports]));
}
getTransport(transport_id) {
return this.transports.get(transport_id);
}
delTransport(transport_id) {
this.transports.delete(transport_id);
}
addTransport(transport) {
this.transports.set(transport.id, transport);
}
async connectTransport(transport_id, dtlsParameters) {
if (!this.transports.has(transport_id)) {
return false;
}
await this.transports.get(transport_id).connect({
dtlsParameters: dtlsParameters,
});
return true;
}
close() {
this.transports.forEach((transport, transport_id) => {
log.debug('Close and delete peer transports', {
transport_id: transport_id,
//transportInternal: transport.internal,
});
transport.close();
this.delTransport(transport_id);
});
}
// ####################################################
// PRODUCER
// ####################################################
getProducers() {
return JSON.parse(JSON.stringify([...this.producers]));
}
getProducer(producer_id) {
return this.producers.get(producer_id);
}
delProducer(producer_id) {
this.producers.delete(producer_id);
}
async createProducer(producerTransportId, producer_rtpParameters, producer_kind, producer_type) {
try {
if (!producerTransportId) {
return 'Invalid producer transport ID';
}
const producerTransport = this.transports.get(producerTransportId);
if (!producerTransport) {
return `Producer transport with ID ${producerTransportId} not found`;
}
const producer = await producerTransport.produce({
kind: producer_kind,
rtpParameters: producer_rtpParameters,
});
if (!producer) {
return `Producer type: ${producer_type} kind: ${producer_kind} not found`;
}
const { id, appData, type, kind, rtpParameters } = producer;
appData.mediaType = producer_type;
this.producers.set(id, producer);
if (['simulcast', 'svc'].includes(type)) {
const { scalabilityMode } = rtpParameters.encodings[0];
const spatialLayer = parseInt(scalabilityMode.substring(1, 2)); // 1/2/3
const temporalLayer = parseInt(scalabilityMode.substring(3, 4)); // 1/2/3
log.debug(`Producer [${type}-${kind}] ----->`, {
scalabilityMode,
spatialLayer,
temporalLayer,
});
} else {
log.debug('Producer ----->', { type: type, kind: kind });
}
producer.on('transportclose', () => {
log.debug('Producer "transportclose" event');
this.closeProducer(id);
});
return producer;
} catch (error) {
log.error('Error creating producer', error.message);
return error.message;
}
}
closeProducer(producer_id) {
if (!this.producers.has(producer_id)) return;
const producer = this.getProducer(producer_id);
const { id, kind, type, appData } = producer;
try {
producer.close();
} catch (error) {
log.warn('Close Producer', error.message);
}
this.delProducer(producer_id);
log.debug('Producer closed and deleted', {
peer_name: this.peer_name,
kind: kind,
type: type,
appData: appData,
producer_id: id,
});
}
// ####################################################
// CONSUMER
// ####################################################
getConsumers() {
return JSON.parse(JSON.stringify([...this.consumers]));
}
getConsumer(consumer_id) {
return this.consumers.get(consumer_id);
}
delConsumer(consumer_id) {
this.consumers.delete(consumer_id);
}
async createConsumer(consumer_transport_id, producer_id, rtpCapabilities) {
try {
if (!consumer_transport_id) {
return 'Invalid consumer transport ID';
}
const consumerTransport = this.transports.get(consumer_transport_id);
if (!consumerTransport) {
return `Consumer transport with id ${consumer_transport_id} not found`;
}
const consumer = await consumerTransport.consume({
producerId: producer_id,
rtpCapabilities,
enableRtx: true, // Enable NACK for OPUS.
paused: false,
});
if (!consumer) {
return `Consumer for producer ID ${producer_id} not found`;
}
const { id, type, kind, rtpParameters, producerPaused } = consumer;
if (['simulcast', 'svc'].includes(type)) {
// simulcast - L1T3/L2T3/L3T3 | svc - L3T3
const { scalabilityMode } = rtpParameters.encodings[0];
const spatialLayer = parseInt(scalabilityMode.substring(1, 2)); // 1/2/3
const temporalLayer = parseInt(scalabilityMode.substring(3, 4)); // 1/2/3
try {
await consumer.setPreferredLayers({
spatialLayer: spatialLayer,
temporalLayer: temporalLayer,
});
log.debug(`Consumer [${type}-${kind}] ----->`, {
scalabilityMode,
spatialLayer,
temporalLayer,
});
} catch (error) {
return `Error to set Consumer preferred layers: ${error.message}`;
}
} else {
log.debug('Consumer ----->', { type: type, kind: kind });
}
this.consumers.set(id, consumer);
consumer.on('transportclose', () => {
log.debug('Consumer "transportclose" event');
this.removeConsumer(id);
});
return {
consumer: consumer,
params: {
producerId: producer_id,
id: id,
kind: kind,
rtpParameters: rtpParameters,
type: type,
producerPaused: producerPaused,
},
};
} catch (error) {
log.error('Error creating consumer', error.message);
return error.message;
}
}
removeConsumer(consumer_id) {
if (!this.consumers.has(consumer_id)) return;
const consumer = this.getConsumer(consumer_id);
const { id, kind, type } = consumer;
try {
consumer.close();
} catch (error) {
log.warn('Close Consumer', error.message);
}
this.delConsumer(consumer_id);
log.debug('Consumer closed and deleted', {
peer_name: this.peer_name,
kind: kind,
type: type,
consumer_id: id,
});
}
};

View File

@ -0,0 +1,577 @@
'use strict';
const config = require('./config');
const Logger = require('./Logger');
const log = new Logger('Room');
module.exports = class Room {
constructor(room_id, worker, io) {
this.id = room_id;
this.worker = worker;
this.webRtcServer = worker.appData.webRtcServer;
this.webRtcServerActive = config.mediasoup.webRtcServerActive;
this.io = io;
this.audioLevelObserver = null;
this.audioLevelObserverEnabled = true;
this.audioLastUpdateTime = 0;
// ##########################
this._isBroadcasting = false;
// ##########################
this._isLocked = false;
this._isLobbyEnabled = false;
this._roomPassword = null;
this._hostOnlyRecording = false;
// ##########################
this._recSyncServerRecording = config?.server?.recording?.enabled || false;
// ##########################
this._moderator = {
audio_start_muted: false,
video_start_hidden: false,
audio_cant_unmute: false,
video_cant_unhide: false,
screen_cant_share: false,
chat_cant_privately: false,
chat_cant_chatgpt: false,
};
this.survey = config.survey;
this.redirect = config.redirect;
this.peers = new Map();
this.bannedPeers = [];
this.webRtcTransport = config.mediasoup.webRtcTransport;
this.router = null;
this.routerSettings = config.mediasoup.router;
this.createTheRouter();
}
// ####################################################
// ROOM INFO
// ####################################################
toJson() {
return {
id: this.id,
broadcasting: this._isBroadcasting,
recSyncServerRecording: this._recSyncServerRecording,
config: {
isLocked: this._isLocked,
isLobbyEnabled: this._isLobbyEnabled,
hostOnlyRecording: this._hostOnlyRecording,
},
moderator: this._moderator,
survey: this.survey,
redirect: this.redirect,
peers: JSON.stringify([...this.peers]),
};
}
// ####################################################
// ROUTER
// ####################################################
createTheRouter() {
const { mediaCodecs } = this.routerSettings;
this.worker
.createRouter({
mediaCodecs,
})
.then((router) => {
this.router = router;
if (this.audioLevelObserverEnabled) {
this.startAudioLevelObservation();
}
this.router.observer.on('close', () => {
log.info('---------------> Router is now closed as the last peer has left the room', {
room: this.id,
});
});
});
}
closeRouter() {
this.router.close();
}
// ####################################################
// PRODUCER AUDIO LEVEL OBSERVER
// ####################################################
async startAudioLevelObservation() {
log.debug('Start audioLevelObserver for signaling active speaker...');
this.audioLevelObserver = await this.router.createAudioLevelObserver({
maxEntries: 1,
threshold: -70,
interval: 100,
});
this.audioLevelObserver.on('volumes', (volumes) => {
this.sendActiveSpeakerVolume(volumes);
});
this.audioLevelObserver.on('silence', () => {
//log.debug('audioLevelObserver', { volume: 'silence' });
});
}
sendActiveSpeakerVolume(volumes) {
try {
if (!Array.isArray(volumes) || volumes.length === 0) {
throw new Error('Invalid volumes array');
}
if (Date.now() > this.audioLastUpdateTime + 100) {
this.audioLastUpdateTime = Date.now();
const { producer, volume } = volumes[0];
const audioVolume = Math.round(Math.pow(10, volume / 70) * 10); // Scale volume to 1-10
if (audioVolume > 1) {
this.peers.forEach((peer) => {
const { id, peer_audio, peer_name } = peer;
peer.producers.forEach((peerProducer) => {
if (peerProducer.id === producer.id && peerProducer.kind === 'audio' && peer_audio) {
const data = {
peer_id: id,
peer_name: peer_name,
audioVolume: audioVolume,
};
// Uncomment the following line for debugging
// log.debug('Sending audio volume', data);
this.sendToAll('audioVolume', data);
}
});
});
}
}
} catch (error) {
log.error('Error sending active speaker volume', error.message);
}
}
addProducerToAudioLevelObserver(producer) {
if (this.audioLevelObserverEnabled) {
this.audioLevelObserver.addProducer(producer);
}
}
getRtpCapabilities() {
return this.router.rtpCapabilities;
}
// ####################################################
// ROOM MODERATOR
// ####################################################
updateRoomModeratorALL(data) {
this._moderator = data;
log.debug('Update room moderator all data', this._moderator);
}
updateRoomModerator(data) {
log.debug('Update room moderator', data);
switch (data.type) {
case 'audio_start_muted':
this._moderator.audio_start_muted = data.status;
break;
case 'video_start_hidden':
this._moderator.video_start_hidden = data.status;
case 'audio_cant_unmute':
this._moderator.audio_cant_unmute = data.status;
break;
case 'video_cant_unhide':
this._moderator.video_cant_unhide = data.status;
case 'screen_cant_share':
this._moderator.screen_cant_share = data.status;
break;
case 'chat_cant_privately':
this._moderator.chat_cant_privately = data.status;
break;
case 'chat_cant_chatgpt':
this._moderator.chat_cant_chatgpt = data.status;
break;
default:
break;
}
}
// ####################################################
// PEERS
// ####################################################
addPeer(peer) {
this.peers.set(peer.id, peer);
}
getPeer(socket_id) {
//
if (!this.peers.has(socket_id)) {
log.error('---> Peer not found for socket ID', socket_id);
return null;
}
const peer = this.peers.get(socket_id);
return peer;
}
getPeers() {
return this.peers;
}
getPeersCount() {
return this.peers.size;
}
getProducerListForPeer() {
const producerList = [];
this.peers.forEach((peer) => {
const { peer_name, peer_info } = peer;
peer.producers.forEach((producer) => {
producerList.push({
producer_id: producer.id,
peer_name: peer_name,
peer_info: peer_info,
type: producer.appData.mediaType,
});
});
});
return producerList;
}
async removePeer(socket_id) {
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return;
}
const { id, peer_name } = peer;
peer.close();
this.peers.delete(socket_id);
if (this.getPeers().size === 0) {
this.closeRouter();
}
const peerTransports = peer.getTransports();
const peerProducers = peer.getProducers();
const peerConsumers = peer.getConsumers();
log.debug('REMOVE PEER', {
peer_id: id,
peer_name: peer_name,
peerTransports: peerTransports,
peerProducers: peerProducers,
peerConsumers: peerConsumers,
});
}
// ####################################################
// WebRTC TRANSPORT
// ####################################################
async createWebRtcTransport(socket_id) {
const { maxIncomingBitrate, initialAvailableOutgoingBitrate, listenInfos } = this.webRtcTransport;
const webRtcTransportOptions = {
...(this.webRtcServerActive ? { webRtcServer: this.webRtcServer } : { listenInfos: listenInfos }),
enableUdp: true,
enableTcp: true,
preferUdp: true,
iceConsentTimeout: 20,
initialAvailableOutgoingBitrate,
};
log.debug('webRtcTransportOptions ----->', webRtcTransportOptions);
const transport = await this.router.createWebRtcTransport(webRtcTransportOptions);
if (!transport) {
return this.callback('[Room|createWebRtcTransport] Failed to create WebRTC transport');
}
const { id, iceParameters, iceCandidates, dtlsParameters } = transport;
if (maxIncomingBitrate) {
try {
await transport.setMaxIncomingBitrate(maxIncomingBitrate);
} catch (error) {
log.debug('Transport setMaxIncomingBitrate error', error.message);
}
}
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return this.callback(`[Room|createWebRtcTransport] Peer object not found for socket ID: ${socket_id}`);
}
const { peer_name } = peer;
transport.on('icestatechange', (iceState) => {
if (iceState === 'disconnected' || iceState === 'closed') {
log.debug('Transport closed "icestatechange" event', {
peer_name: peer_name,
transport_id: id,
iceState: iceState,
});
transport.close();
}
});
transport.on('sctpstatechange', (sctpState) => {
log.debug('Transport "sctpstatechange" event', {
peer_name: peer_name,
transport_id: id,
sctpState: sctpState,
});
});
transport.on('dtlsstatechange', (dtlsState) => {
if (dtlsState === 'failed' || dtlsState === 'closed') {
log.debug('Transport closed "dtlsstatechange" event', {
peer_name: peer_name,
transport_id: id,
dtlsState: dtlsState,
});
transport.close();
}
});
transport.on('close', () => {
log.debug('Transport closed', { peer_name: peer_name, transport_id: transport.id });
});
peer.addTransport(transport);
log.debug('Transport created', { transportId: id });
return {
id: id,
iceParameters: iceParameters,
iceCandidates: iceCandidates,
dtlsParameters: dtlsParameters,
};
}
async connectPeerTransport(socket_id, transport_id, dtlsParameters) {
try {
if (!socket_id || !transport_id || !dtlsParameters) {
return this.callback('[Room|connectPeerTransport] Invalid input parameters');
}
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return this.callback(`[Room|connectPeerTransport] Peer object not found for socket ID: ${socket_id}`);
}
const connectTransport = await peer.connectTransport(transport_id, dtlsParameters);
if (!connectTransport) {
return this.callback(`[Room|connectPeerTransport] error: Transport with ID ${transport_id} not found`);
}
return '[Room|connectPeerTransport] done';
} catch (error) {
log.error('Error connecting peer transport', error.message);
return this.callback(`[Room|connectPeerTransport] error: ${error.message}`);
}
}
// ####################################################
// PRODUCE
// ####################################################
async produce(socket_id, producerTransportId, rtpParameters, kind, type) {
//
if (!socket_id || !producerTransportId || !rtpParameters || !kind || !type) {
return this.callback('[Room|produce] Invalid input parameters');
}
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return this.callback(`[Room|produce] Peer object not found for socket ID: ${socket_id}`);
}
const peerProducer = await peer.createProducer(producerTransportId, rtpParameters, kind, type);
if (!peerProducer || !peerProducer.id) {
return this.callback(`[Room|produce] Peer producer error: '${peerProducer}'`);
}
const { id } = peerProducer;
const { peer_name, peer_info } = peer;
this.broadCast(socket_id, 'newProducers', [
{
producer_id: id,
producer_socket_id: socket_id,
peer_name: peer_name,
peer_info: peer_info,
type: type,
},
]);
return id;
}
closeProducer(socket_id, producer_id) {
if (!socket_id || !producer_id) return;
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return;
}
peer.closeProducer(producer_id);
}
// ####################################################
// CONSUME
// ####################################################
async consume(socket_id, consumer_transport_id, producer_id, rtpCapabilities) {
try {
if (!socket_id || !consumer_transport_id || !producer_id || !rtpCapabilities) {
return this.callback('[Room|consume] Invalid input parameters');
}
if (!this.router.canConsume({ producerId: producer_id, rtpCapabilities })) {
log.warn('Cannot consume', {
socket_id,
consumer_transport_id,
producer_id,
});
return this.callback(`[Room|consume] Room router cannot consume producer_id: '${producer_id}'`);
}
const peer = this.getPeer(socket_id);
if (!peer || typeof peer !== 'object') {
return this.callback(`[Room|consume] Peer object not found for socket ID: ${socket_id}`);
}
const peerConsumer = await peer.createConsumer(consumer_transport_id, producer_id, rtpCapabilities);
if (!peerConsumer || !peerConsumer.consumer || !peerConsumer.params) {
log.debug('peerConsumer or params are not defined');
return this.callback(`[Room|consume] peerConsumer error: '${peerConsumer}'`);
}
const { consumer, params } = peerConsumer;
const { id, kind } = consumer;
consumer.on('producerclose', () => {
log.debug('Consumer closed due to "producerclose" event');
peer.removeConsumer(id);
// Notify the client that consumer is closed
this.send(socket_id, 'consumerClosed', {
consumer_id: id,
consumer_kind: kind,
});
});
return params;
} catch (error) {
log.error('Error occurred during consumption', error.message);
return this.callback(`[Room|consume] ${error.message}`);
}
}
// ####################################################
// HANDLE BANNED PEERS
// ####################################################
addBannedPeer(uuid) {
if (!this.bannedPeers.includes(uuid)) {
this.bannedPeers.push(uuid);
log.debug('Added to the banned list', {
uuid: uuid,
banned: this.bannedPeers,
});
}
}
isBanned(uuid) {
return this.bannedPeers.includes(uuid);
}
// ####################################################
// ROOM STATUS
// ####################################################
// GET
isBroadcasting() {
return this._isBroadcasting;
}
getPassword() {
return this._roomPassword;
}
// BOOL
isLocked() {
return this._isLocked;
}
isLobbyEnabled() {
return this._isLobbyEnabled;
}
isHostOnlyRecording() {
return this._hostOnlyRecording;
}
// SET
setIsBroadcasting(status) {
this._isBroadcasting = status;
}
setLocked(status, password) {
this._isLocked = status;
this._roomPassword = password;
}
setLobbyEnabled(status) {
this._isLobbyEnabled = status;
}
setHostOnlyRecording(status) {
this._hostOnlyRecording = status;
}
// ####################################################
// ERRORS
// ####################################################
callback(message) {
return { error: message };
}
// ####################################################
// SENDER
// ####################################################
broadCast(socket_id, action, data) {
for (let otherID of Array.from(this.peers.keys()).filter((id) => id !== socket_id)) {
this.send(otherID, action, data);
}
}
sendTo(socket_id, action, data) {
for (let peer_id of Array.from(this.peers.keys()).filter((id) => id === socket_id)) {
this.send(peer_id, action, data);
}
}
sendToAll(action, data) {
for (let peer_id of Array.from(this.peers.keys())) {
this.send(peer_id, action, data);
}
}
send(socket_id, action, data) {
this.io.to(socket_id).emit(action, data);
}
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,121 @@
'use strict';
const jwt = require('jsonwebtoken');
const CryptoJS = require('crypto-js');
const config = require('./config');
const { v4: uuidV4 } = require('uuid');
const JWT_KEY = (config.jwt && config.jwt.key) || 'mirotalksfu_jwt_secret';
const JWT_EXP = (config.jwt && config.jwt.exp) || '1h';
module.exports = class ServerApi {
constructor(host = null, authorization = null) {
this._host = host;
this._authorization = authorization;
this._api_key_secret = config.api.keySecret;
}
isAuthorized() {
if (this._authorization != this._api_key_secret) return false;
return true;
}
getMeetings(roomList) {
const meetings = Array.from(roomList.entries()).map(([id, room]) => {
const peers = Array.from(room.peers.values()).map(
({
peer_info: {
peer_name,
peer_presenter,
peer_video,
peer_audio,
peer_screen,
peer_hand,
os_name,
os_version,
browser_name,
browser_version,
},
}) => ({
name: peer_name,
presenter: peer_presenter,
video: peer_video,
audio: peer_audio,
screen: peer_screen,
hand: peer_hand,
os: os_name ? `${os_name} ${os_version}` : '',
browser: browser_name ? `${browser_name} ${browser_version}` : '',
}),
);
return {
roomId: id,
peers: peers,
};
});
return meetings;
}
getMeetingURL() {
return 'https://' + this._host + '/join/' + uuidV4();
}
getJoinURL(data) {
// Get data
const { room, roomPassword, name, audio, video, screen, hide, notify, token } = data;
const roomValue = room || uuidV4();
const nameValue = name || 'User-' + this.getRandomNumber();
const roomPasswordValue = roomPassword || false;
const audioValue = audio || false;
const videoValue = video || false;
const screenValue = screen || false;
const hideValue = hide || false;
const notifyValue = notify || false;
const jwtToken = token ? '&token=' + this.getToken(token) : '';
const joinURL =
'https://' +
this._host +
'/join?' +
`room=${roomValue}` +
`&roomPassword=${roomPasswordValue}` +
`&name=${encodeURIComponent(nameValue)}` +
`&audio=${audioValue}` +
`&video=${videoValue}` +
`&screen=${screenValue}` +
`&hide=${hideValue}` +
`&notify=${notifyValue}` +
jwtToken;
return joinURL;
}
getToken(token) {
if (!token) return '';
const { username = 'username', password = 'password', presenter = false, expire } = token;
const expireValue = expire || JWT_EXP;
// Constructing payload
const payload = {
username: String(username),
password: String(password),
presenter: String(presenter),
};
// Encrypt payload using AES encryption
const payloadString = JSON.stringify(payload);
const encryptedPayload = CryptoJS.AES.encrypt(payloadString, JWT_KEY).toString();
// Constructing JWT token
const jwtToken = jwt.sign({ data: encryptedPayload }, JWT_KEY, { expiresIn: expireValue });
return jwtToken;
}
getRandomNumber() {
return Math.floor(Math.random() * 999999);
}
};

View File

@ -0,0 +1,66 @@
'use strict';
const xss = require('xss');
const Logger = require('./Logger');
const log = new Logger('Xss');
const checkXSS = (dataObject) => {
try {
if (Array.isArray(dataObject)) {
if (Object.keys(dataObject).length > 0 && typeof dataObject[0] === 'object') {
dataObject.forEach((obj) => {
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
let objectJson = objectToJSONString(obj[key]);
if (objectJson) {
let jsonString = xss(objectJson);
let jsonObject = JSONStringToObject(jsonString);
if (jsonObject) {
obj[key] = jsonObject;
}
}
}
}
});
log.debug('XSS Array of Object sanitization done');
return dataObject;
}
} else if (typeof dataObject === 'object') {
let objectJson = objectToJSONString(dataObject);
if (objectJson) {
let jsonString = xss(objectJson);
let jsonObject = JSONStringToObject(jsonString);
if (jsonObject) {
log.debug('XSS Object sanitization done');
return jsonObject;
}
}
} else if (typeof dataObject === 'string' || dataObject instanceof String) {
log.debug('XSS String sanitization done');
return xss(dataObject);
}
log.warn('XSS not sanitized', dataObject);
return dataObject;
} catch (error) {
log.error('XSS error', { data: dataObject, error: error });
return dataObject;
}
};
function objectToJSONString(dataObject) {
try {
return JSON.stringify(dataObject);
} catch (error) {
return false;
}
}
function JSONStringToObject(jsonString) {
try {
return JSON.parse(jsonString);
} catch (error) {
return false;
}
}
module.exports = checkXSS;

View File

@ -0,0 +1,433 @@
'use strict';
const os = require('os');
// https://api.ipify.org
function getIPv4() {
const ifaces = os.networkInterfaces();
for (const interfaceName in ifaces) {
const iface = ifaces[interfaceName];
for (const { address, family, internal } of iface) {
if (family === 'IPv4' && !internal) {
return address;
}
}
}
return '0.0.0.0'; // Default to 0.0.0.0 if no external IPv4 address found
}
const IPv4 = getIPv4();
const numWorkers = require('os').cpus().length;
module.exports = {
console: {
/*
timeZone: Time Zone corresponding to timezone identifiers from the IANA Time Zone Database es 'Europe/Rome' default UTC
*/
timeZone: 'UTC',
debug: true,
colors: true,
},
server: {
listen: {
// app listen on
ip: '0.0.0.0',
port: process.env.PORT || 3010,
},
ssl: {
// ssl/README.md
cert: '../ssl/cert.pem',
key: '../ssl/key.pem',
},
cors: {
/*
origin: Allow specified origin es ['https://example.com', 'https://subdomain.example.com', 'http://localhost:3010'] or all origins if not specified
methods: Allow only GET and POST methods
*/
origin: '*',
methods: ['GET', 'POST'],
},
recording: {
/*
The recording will be saved to the directory designated within your Server app/<dir>
Note: if you use Docker: Create the "app/rec" directory, configure it as a volume in docker-compose.yml,
ensure proper permissions, and start the Docker container.
*/
dir: 'rec',
enabled: false,
},
},
middleware: {
/*
Middleware:
- IP Whitelist: Access to the instance is restricted to only the specified IP addresses in the allowed list. This feature is disabled by default.
- ...
*/
IpWhitelist: {
enabled: false,
allowed: ['127.0.0.1', '::1'],
},
},
api: {
// Default secret key for app/api
keySecret: 'mirotalksfu_default_secret',
// Define which endpoints are allowed
allowed: {
meetings: false,
meeting: true,
join: true,
token: false,
slack: true,
//...
},
},
jwt: {
/*
JWT https://jwt.io/
Securely manages credentials for host configurations and user authentication, enhancing security and streamlining processes.
*/
key: 'mirotalksfu_jwt_secret',
exp: '1h',
},
host: {
/*
Host Protection (default: false)
To enhance host security, enable host protection - user auth and provide valid
usernames and passwords in the users array or active users_from_db using users_api_endpoint for check.
*/
protected: false,
user_auth: false,
users_from_db: false, // if true ensure that api.token is also set to true.
//users_api_endpoint: 'http://localhost:9000/api/v1/user/isAuth',
users_api_endpoint: 'https://webrtc.mirotalk.com/api/v1/user/isAuth',
users_api_secret_key: 'mirotalkweb_default_secret',
users: [
{
username: 'username',
password: 'password',
},
{
username: 'username2',
password: 'password2',
},
//...
],
},
presenters: {
list: [
/*
By default, the presenter is identified as the first participant to join the room, distinguished by their username and UUID.
Additional layers can be added to specify valid presenters and co-presenters by setting designated usernames.
*/
'Miroslav Pejic',
'miroslav.pejic.85@gmail.com',
],
join_first: true, // Set to true for traditional behavior, false to prioritize presenters
},
chatGPT: {
/*
ChatGPT
1. Goto https://platform.openai.com/
2. Create your account
3. Generate your APIKey https://platform.openai.com/account/api-keys
*/
enabled: false,
basePath: 'https://api.openai.com/v1/',
apiKey: '',
model: 'gpt-3.5-turbo',
max_tokens: 1000,
temperature: 0,
},
email: {
/*
Configure email settings for notifications or alerts
Refer to the documentation for Gmail configuration: https://support.google.com/mail/answer/185833?hl=en
*/
alert: false,
host: 'smtp.gmail.com',
port: 587,
username: 'your_username',
password: 'your_password',
sendTo: 'sfu.mirotalk@gmail.com',
},
ngrok: {
/*
Ngrok
1. Goto https://ngrok.com
2. Get started for free
3. Copy YourNgrokAuthToken: https://dashboard.ngrok.com/get-started/your-authtoken
*/
authToken: '',
},
sentry: {
/*
Sentry
1. Goto https://sentry.io/
2. Create account
3. On dashboard goto Settings/Projects/YourProjectName/Client Keys (DSN)
*/
enabled: false,
DSN: '',
tracesSampleRate: 0.5,
},
slack: {
/*
Slack
1. Goto https://api.slack.com/apps/
2. Create your app
3. On Settings - Basic Information - App Credentials, chose your Signing Secret
4. Create a Slash Commands and put as Request URL: https://your.domain.name/slack
*/
enabled: false,
signingSecret: '',
},
IPLookup: {
/*
GeoJS
https://www.geojs.io/docs/v1/endpoints/geo/
*/
enabled: false,
getEndpoint(ip) {
return `https://get.geojs.io/v1/ip/geo/${ip}.json`;
},
},
survey: {
/*
QuestionPro
1. GoTo https://www.questionpro.com/
2. Create your account
3. Create your custom survey
*/
enabled: false,
url: '',
},
redirect: {
/*
Redirect URL on leave room
Upon leaving the room, users who either opt out of providing feedback or if the survey is disabled
will be redirected to a specified URL. If enabled false the default '/newroom' URL will be used.
*/
enabled: false,
url: '',
},
ui: {
/*
Customize your MiroTalk instance
*/
brand: {
app: {
name: 'MiroTalk SFU',
title: 'MiroTalk SFU<br />Free browser based Real-time video calls.<br />Simple, Secure, Fast.',
description:
'Start your next video call with a single click. No download, plug-in, or login is required. Just get straight to talking, messaging, and sharing your screen.',
},
site: {
title: 'MiroTalk SFU, Free Video Calls, Messaging and Screen Sharing',
icon: '../images/logo.svg',
appleTouchIcon: '../images/logo.svg',
},
meta: {
description:
'MiroTalk SFU powered by WebRTC and mediasoup, Real-time Simple Secure Fast video calls, messaging and screen sharing capabilities in the browser.',
keywords:
'webrtc, miro, mediasoup, mediasoup-client, self hosted, voip, sip, real-time communications, chat, messaging, meet, webrtc stun, webrtc turn, webrtc p2p, webrtc sfu, video meeting, video chat, video conference, multi video chat, multi video conference, peer to peer, p2p, sfu, rtc, alternative to, zoom, microsoft teams, google meet, jitsi, meeting',
},
og: {
type: 'app-webrtc',
siteName: 'MiroTalk SFU',
title: 'Click the link to make a call.',
description: 'MiroTalk SFU calling provides real-time video calls, messaging and screen sharing.',
image: 'https://sfu.mirotalk.com/images/mirotalksfu.png',
},
html: {
features: true,
teams: true, // Please keep me always visible, thank you!
tryEasier: true,
poweredBy: true,
sponsors: true,
advertisers: true,
footer: true,
},
//...
},
/*
Toggle the visibility of specific HTML elements within the room
*/
buttons: {
main: {
shareButton: true, // presenter
hideMeButton: true,
startAudioButton: true,
startVideoButton: true,
startScreenButton: true,
swapCameraButton: true,
chatButton: true,
raiseHandButton: true,
transcriptionButton: true,
whiteboardButton: true,
emojiRoomButton: true,
settingsButton: true,
aboutButton: true, // Please keep me always visible, thank you!
exitButton: true,
},
settings: {
fileSharing: true,
lockRoomButton: true, // presenter
unlockRoomButton: true, // presenter
broadcastingButton: true, // presenter
lobbyButton: true, // presenter
sendEmailInvitation: true, // presenter
micOptionsButton: true, // presenter
tabModerator: true, // presenter
tabRecording: true,
host_only_recording: true, // presenter
pushToTalk: true,
},
producerVideo: {
videoPictureInPicture: true,
fullScreenButton: true,
snapShotButton: true,
muteAudioButton: true,
videoPrivacyButton: true,
},
consumerVideo: {
videoPictureInPicture: true,
fullScreenButton: true,
snapShotButton: true,
sendMessageButton: true,
sendFileButton: true,
sendVideoButton: true,
muteVideoButton: true,
muteAudioButton: true,
audioVolumeInput: true, // Disabled for mobile
geolocationButton: true, // Presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
videoOff: {
sendMessageButton: true,
sendFileButton: true,
sendVideoButton: true,
muteAudioButton: true,
audioVolumeInput: true, // Disabled for mobile
geolocationButton: true, // Presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
chat: {
chatPinButton: true,
chatMaxButton: true,
chatSaveButton: true,
chatEmojiButton: true,
chatMarkdownButton: true,
chatSpeechStartButton: true,
chatGPT: true,
},
participantsList: {
saveInfoButton: true, // presenter
sendFileAllButton: true, // presenter
ejectAllButton: true, // presenter
sendFileButton: true, // presenter & guests
geoLocationButton: true, // presenter
banButton: true, // presenter
ejectButton: true, // presenter
},
whiteboard: {
whiteboardLockButton: true, // presenter
},
//...
},
},
stats: {
/*
Umami: https://github.com/umami-software/umami
We use our Self-hosted Umami to track aggregated usage statistics in order to improve our service.
*/
enabled: true,
src: 'https://stats.mirotalk.com/script.js',
id: '41d26670-f275-45bb-af82-3ce91fe57756',
},
mediasoup: {
// Worker settings
numWorkers: numWorkers,
worker: {
logLevel: 'error',
logTags: ['info', 'ice', 'dtls', 'rtp', 'srtp', 'rtcp', 'rtx', 'bwe', 'score', 'simulcast', 'svc', 'sctp'],
},
// Router settings
router: {
mediaCodecs: [
{
kind: 'audio',
mimeType: 'audio/opus',
clockRate: 48000,
channels: 2,
},
{
kind: 'video',
mimeType: 'video/VP8',
clockRate: 90000,
parameters: {
'x-google-start-bitrate': 1000,
},
},
{
kind: 'video',
mimeType: 'video/VP9',
clockRate: 90000,
parameters: {
'profile-id': 2,
'x-google-start-bitrate': 1000,
},
},
{
kind: 'video',
mimeType: 'video/h264',
clockRate: 90000,
parameters: {
'packetization-mode': 1,
'profile-level-id': '4d0032',
'level-asymmetry-allowed': 1,
'x-google-start-bitrate': 1000,
},
},
{
kind: 'video',
mimeType: 'video/h264',
clockRate: 90000,
parameters: {
'packetization-mode': 1,
'profile-level-id': '42e01f',
'level-asymmetry-allowed': 1,
'x-google-start-bitrate': 1000,
},
},
],
},
// WebRtcServerOptions
webRtcServerActive: false,
webRtcServerOptions: {
listenInfos: [
{ protocol: 'udp', ip: '0.0.0.0', announcedAddress: IPv4, port: 44444 },
{ protocol: 'tcp', ip: '0.0.0.0', announcedAddress: IPv4, port: 44444 },
// { protocol: 'udp', ip: '0.0.0.0', announcedAddress: IPv4, portRange: { min: 44444, max: 44444 + numWorkers }},
// { protocol: 'tcp', ip: '0.0.0.0', announcedAddress: IPv4, portRange: { min: 44444, max: 44444 + numWorkers }},
],
},
// WebRtcTransportOptions
webRtcTransport: {
listenInfos: [
{ protocol: 'udp', ip: '0.0.0.0', announcedAddress: IPv4, portRange: { min: 40000, max: 40100 } },
{ protocol: 'tcp', ip: '0.0.0.0', announcedAddress: IPv4, portRange: { min: 40000, max: 40100 } },
],
initialAvailableOutgoingBitrate: 1000000,
minimumAvailableOutgoingBitrate: 600000,
maxSctpMessageSize: 262144,
maxIncomingBitrate: 1500000,
},
//announcedAddress: replace by 'public static IPV4 address' https://api.ipify.org (type string --> 'xx.xxx.xxx.xx' not xx.xxx.xxx.xx)
//announcedAddress: '' will be auto-detected on server start, for docker localPC set '127.0.0.1' otherwise the 'public static IPV4 address'
},
};

View File

@ -0,0 +1,150 @@
'use-strict';
const nodemailer = require('nodemailer');
const config = require('../config');
const Logger = require('../Logger');
const log = new Logger('NodeMailer');
// ####################################################
// EMAIL CONFIG
// ####################################################
const EMAIL_HOST = config.email ? config.email.host : false;
const EMAIL_PORT = config.email ? config.email.port : false;
const EMAIL_USERNAME = config.email ? config.email.username : false;
const EMAIL_PASSWORD = config.email ? config.email.password : false;
const EMAIL_SEND_TO = config.email ? config.email.sendTo : false;
const EMAIL_ALERT = config.email ? config.email.alert : false;
log.info('Email', {
alert: EMAIL_ALERT,
host: EMAIL_HOST,
port: EMAIL_PORT,
username: EMAIL_USERNAME,
password: EMAIL_PASSWORD,
});
const transport = nodemailer.createTransport({
host: EMAIL_HOST,
port: EMAIL_PORT,
auth: {
user: EMAIL_USERNAME,
pass: EMAIL_PASSWORD,
},
});
// ####################################################
// EMAIL SEND ALERTS AND NOTIFICATIONS
// ####################################################
function sendEmailAlert(event, data) {
if (!EMAIL_ALERT || !EMAIL_HOST || !EMAIL_PORT || !EMAIL_USERNAME || !EMAIL_PASSWORD || !EMAIL_SEND_TO) return;
log.info('sendEMailAlert', {
event: event,
data: data,
});
let subject = false;
let body = false;
switch (event) {
case 'join':
subject = getJoinRoomSubject(data);
body = getJoinRoomBody(data);
break;
// ...
default:
break;
}
if (subject && body) sendEmail(subject, body);
}
function sendEmail(subject, body) {
transport
.sendMail({
from: EMAIL_USERNAME,
to: EMAIL_SEND_TO,
subject: subject,
html: body,
})
.catch((err) => log.error(err));
}
// ####################################################
// EMAIL TEMPLATES
// ####################################################
function getJoinRoomSubject(data) {
const { room_id } = data;
return `MiroTalk SFU - New user Join to Room ${room_id}`;
}
function getJoinRoomBody(data) {
const { peer_name, room_id, domain, os, browser } = data;
const currentDataTime = getCurrentDataTime();
const localDomains = ['localhost', '127.0.0.1'];
const currentDomain = localDomains.some((localDomain) => domain.includes(localDomain))
? `${domain}:${config.server.listen.port}`
: domain;
const room_join = `https://${currentDomain}/join/`;
return `
<h1>New user join</h1>
<style>
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #dddddd;
}
</style>
<table>
<tr>
<td>User</td>
<td>${peer_name}</td>
</tr>
<tr>
<td>Os</td>
<td>${os}</td>
</tr>
<tr>
<td>Browser</td>
<td>${browser}</td>
</tr>
<tr>
<td>Room</td>
<td>${room_join}${room_id}</td>
</tr>
<tr>
<td>Date, Time</td>
<td>${currentDataTime}</td>
</tr>
</table>
`;
}
// ####################################################
// UTILITY
// ####################################################
function getCurrentDataTime() {
const currentTime = new Date().toLocaleString('en-US', log.tzOptions);
const milliseconds = String(new Date().getMilliseconds()).padStart(3, '0');
return `${currentTime}:${milliseconds}`;
}
module.exports = {
sendEmailAlert,
};

View File

@ -0,0 +1,24 @@
'use strict';
const config = require('../config');
const Logger = require('../Logger');
const log = new Logger('RestrictAccessByIP');
const IpWhitelistEnabled = config.middleware ? config.middleware.IpWhitelist.enabled : false;
const allowedIPs = config.middleware ? config.middleware.IpWhitelist.allowed : [];
const restrictAccessByIP = (req, res, next) => {
if (!IpWhitelistEnabled) return next();
//
const clientIP =
req.headers['x-forwarded-for'] || req.headers['X-Forwarded-For'] || req.socket.remoteAddress || req.ip;
log.debug('Check IP', clientIP);
if (allowedIPs.includes(clientIP)) {
next();
} else {
log.info('Forbidden: Access denied from this IP address', { clientIP: clientIP });
res.status(403).json({ error: 'Forbidden', message: 'Access denied from this IP address.' });
}
};
module.exports = restrictAccessByIP;

View File

@ -0,0 +1,159 @@
'use strict';
const config = require('../config');
const net = require('net');
/*
Run: node bindable.js
In networking, "bindable" refers to the ability to assign or allocate a specific IP address and port combination
to a network service or application. Binding an IP address and port allows the service or application to listen for
incoming network connections on that particular address and port.
When we say an IP address and port are "bindable," it means that there are no conflicts or issues preventing the service
or application from using that specific combination. In other words, the IP address is available, and the port is not already
in use by another process or service on the same machine.
If an IP address and port are bindable, it indicates that the network service or application can successfully bind to that
combination, allowing it to accept incoming connections and communicate over the network. On the other hand, if the IP address
and port are not bindable, it suggests that there may be conflicts or restrictions preventing the service or application
from using them, such as another process already listening on the same IP address and port.
*/
async function main() {
// Server listen
const serverListenIp = config.server.listen.ip;
const serverListenPort = config.server.listen.port;
// WebRtcServerActive
const webRtcServerActive = config.mediasoup.webRtcServerActive;
// WebRtcTransportOptions
const webRtcTransportIpInfo = config.mediasoup.webRtcTransport.listenInfos[0];
const webRtcTransportIpAddress =
webRtcTransportIpInfo.ip !== '0.0.0.0' ? webRtcTransportIpInfo.ip : webRtcTransportIpInfo.announcedAddress;
// WorkersOptions | webRtcTransportOptions
const workers = config.mediasoup.numWorkers;
const { min, max } = config.mediasoup.webRtcTransport.listenInfos[0].portRange;
const rtcMinPort = config.mediasoup.worker.rtcMinPort || min || 40000;
const rtcMaxPort = config.mediasoup.worker.rtcMaxPort || max || 40100;
console.log('==================================');
console.log('checkServerListenPorts');
console.log('==================================');
await checkServerListenPorts(serverListenIp, serverListenPort);
console.log('==================================');
console.log('checkWebRtcTransportPorts');
console.log('==================================');
await checkWebRtcTransportPorts(webRtcTransportIpAddress, rtcMinPort, rtcMaxPort);
if (webRtcServerActive) {
console.log('==================================');
console.log('checkWebRtcServerPorts');
console.log('==================================');
// WebRtcServerOptions
const webRtcServerIpInfo = config.mediasoup.webRtcServerOptions.listenInfos[0];
const webRtcServerIpAddress =
webRtcServerIpInfo.ip !== '0.0.0.0' ? webRtcServerIpInfo.ip : webRtcServerIpInfo.announcedAddress;
const webRtcServerStartPort = webRtcServerIpInfo.port;
await checkWebRtcServerPorts(webRtcServerIpAddress, webRtcServerStartPort, workers);
}
}
/**
* Check if Server listen port is bindable
* @param {string} ipAddress
* @param {integer} port
*/
async function checkServerListenPorts(ipAddress, port) {
const bindable = await isBindable(ipAddress, port);
if (bindable) {
console.log(`${ipAddress}:${port} is bindable 🟢`);
} else {
console.log(`${ipAddress}:${port} is not bindable 🔴`);
}
}
/**
* Check if WebRtcServer ports are bindable
* @param {string} ipAddress
* @param {integer} startPort
* @param {integer} workers
*/
async function checkWebRtcServerPorts(ipAddress, startPort, workers) {
let port = startPort;
for (let i = 0; i < workers; i++) {
try {
const bindable = await isBindable(ipAddress, port);
if (bindable) {
console.log(`${ipAddress}:${port} is bindable 🟢`);
} else {
console.log(`${ipAddress}:${port} is not bindable 🔴`);
}
port++;
} catch (err) {
console.error('Error occurred:', err);
}
}
}
/**
* Check if WebRtcTransport Worker ports are bindable
* @param {string} ipAddress
* @param {integer} minPort
* @param {integer} maxPort
*/
async function checkWebRtcTransportPorts(ipAddress, minPort, maxPort) {
let port = minPort;
for (let i = 0; i <= maxPort - minPort; i++) {
try {
const bindable = await isBindable(ipAddress, port);
if (bindable) {
console.log(`${ipAddress}:${port} is bindable 🟢`);
} else {
console.log(`${ipAddress}:${port} is not bindable 🔴`);
}
port++;
} catch (err) {
console.error('Error occurred:', err);
}
}
}
/**
* Check if ipAddress:port are bindable
* @param {string} ipAddress
* @param {integer} port
* @returns {Promise<boolean>} A promise that resolves to true if the address is bindable, false otherwise.
*/
async function isBindable(ipAddress, port) {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.once('error', (err) => {
if (err.code === 'EADDRINUSE') {
resolve(false); // Address is already in use
} else {
reject(err); // Other error occurred
}
});
server.once('listening', () => {
server.close();
resolve(true); // Address is bindable
});
server.listen(port, ipAddress);
});
}
main().catch((err) => {
console.error('Error occurred in main function:', err.message);
});

View File

@ -0,0 +1,22 @@
## Self-signed certificate
[What is self-signed-certificate](https://en.wikipedia.org/wiki/Self-signed_certificate)
![mirotalksfu-https](https.png)
```bash
# install openssl 4 ubuntu
apt install openssl
# install openssl 4 mac
brew install openssl
# self-signed certificate
openssl genrsa -out key.pem
openssl req -new -key key.pem -out csr.pem
openssl x509 -req -days 9999 -in csr.pem -signkey key.pem -out cert.pem
rm csr.pem
# https://www.sslchecker.com/certdecoder
```
For trusted certificate, take a look at [Let's Encrypt](https://letsencrypt.org/i) and [Certbot](https://certbot.eff.org/).

View File

@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDlTCCAn0CFBM91H+g2aRKsaRrCmo3NdYjwfWUMA0GCSqGSIb3DQEBCwUAMIGG
MQswCQYDVQQGEwJJVDEOMAwGA1UECAwFSXRhbHkxETAPBgNVBAoMCE1pcm9UYWxr
MQ8wDQYDVQQLDAZXZWJSVEMxFzAVBgNVBAMMDk1pcm9zbGF2IFBlamljMSowKAYJ
KoZIhvcNAQkBFhttaXJvc2xhdi5wZWppYy44NUBnbWFpbC5jb20wHhcNMjEwODEx
MTUwOTUzWhcNNDgxMjI2MTUwOTUzWjCBhjELMAkGA1UEBhMCSVQxDjAMBgNVBAgM
BUl0YWx5MREwDwYDVQQKDAhNaXJvVGFsazEPMA0GA1UECwwGV2ViUlRDMRcwFQYD
VQQDDA5NaXJvc2xhdiBQZWppYzEqMCgGCSqGSIb3DQEJARYbbWlyb3NsYXYucGVq
aWMuODVAZ21haWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
rn2tC9W6wqjDI7B/4LfEnE4ILBOdwa9889QjmWUToKua7iXTpaSNP3SefKY50Q8T
BFfkZXEGyqAESBUn2rYeHtebgLQTKHsixhSCdHqpBDOyYFeTywGiRP4gQHFKbExd
X2AAD1ptTjHVuSlg/ojWstESBh/4TktifKzy3PKVKX6p889eDfyJtlv0PADAkon/
rZp3hHq0FORhvQEER1sm6g58WyIfqGWjW7bb7/bkyS1baQI16fPeexfu1Rs7y4kx
gX7+9/oA40D3rz0Wf378PTwVzbYCF+hZo/H9yJGTUAZSz84zNbSLvKBZFPfabA2A
l92uPgNWoct06uf7ubEJhwIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQAyH68yi+mf
2HNwEscSLN1oU9uXNC4hwHi2xoPrpxoLsbhoc/aVhgd5peOEmIlOQPQofCO/JSKt
HqidURSt8U+WrcsXz3HTdq+m/Qw3E/BA5CDXL/LUmIh43cZzkWSawx2EocJr7g1W
JeAtUt8xpDtuLlTMjoGGmTsKQG7ub8NcYN7EEqWa2v+2qSTqvhASMs+cf7DT2HZI
I3q2v6l1N+DcpO8ey8dbLhbhd4d+bGjyjEcT+clDHFrKsqiYDCS99sOmedmHoyZk
+h+CzXICtqFSrAAGE/PoetQtJlojwnu9mJN/xj3i/zJTZTRh3jOGF8Hfg2bvwgdg
vMYRLwtknqya
-----END CERTIFICATE-----

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEArn2tC9W6wqjDI7B/4LfEnE4ILBOdwa9889QjmWUToKua7iXT
paSNP3SefKY50Q8TBFfkZXEGyqAESBUn2rYeHtebgLQTKHsixhSCdHqpBDOyYFeT
ywGiRP4gQHFKbExdX2AAD1ptTjHVuSlg/ojWstESBh/4TktifKzy3PKVKX6p889e
DfyJtlv0PADAkon/rZp3hHq0FORhvQEER1sm6g58WyIfqGWjW7bb7/bkyS1baQI1
6fPeexfu1Rs7y4kxgX7+9/oA40D3rz0Wf378PTwVzbYCF+hZo/H9yJGTUAZSz84z
NbSLvKBZFPfabA2Al92uPgNWoct06uf7ubEJhwIDAQABAoIBAAhskNota2LSevlS
IBpdROS277YRDGC5dDLhXwac1qG/Jy+wK9OnahpSKwShkdECBU0EYUZ0ent11j8U
pmPsvu+GQT+pcfNWXotpmhK9iUNmq4nzMHNwlMD3896omYs49JkSLW6QUw6fYU4b
LU+ck6D2bwRUrsw433xdbSw1mfXyzyCIWfPNzRmkEcUkCe1RHkGqFv8FrePhezOB
tByAwQQLtBt7FMioTSCedOe7B+tuxwqj5Px7Vr8K3x/PKccSId6hS2ihnptYB+n7
3eAxoa049wazsW3SXJQrTxUB1z2bh2p2lA0AedgVNme1FCq4zzA7qa2WppcV2ava
Gu+dCgECgYEA1xjLFQmiUqCJVqA99DZNvgiPecuHSe2+2QRIk10uUQcxQAF8K9XT
ORpO05lc0ccPZt7tIbQytsnt/NxL2mnvXQ+dzzrQCsPu/HH8fLEaJ4xBdxuIFeDU
qCBKYlckCQkDwnNYUqCZCNfxb0Csx98RBDYlwDwZa0hFsLPbGen538cCgYEAz6wh
iRXZVTqfhy3meWYFmHqeMb0agFugl4d0Lwl/kI/0M62Jc1u1OU+uI6u5fuevEeBB
xYjnaDpBuCWccLw6R5luc98QIbxldbx2A07rUGo2JlQafmDX8wI1GVBXgzOw7HAK
jHhCw+ZgtvF2c1XjaTPaunKDomPX6Pjt6R23CEECgYEArRRvPbNt4Wz6djElCSC1
N+ftg4TJjSx4eGog+CtvvJW8BJPtVdyORZGswknSzZ6O/yj8yTUV5c3g6apegxbh
HBIX2wupIjB9WrdiAvgDYrVSbEREIc6zb8Hj+PPDtF2Dn/FurbY6zkntJad2ILKX
H7tubxwtHA2gvkpLULPcdDsCgYAxm3WbUHvM7ycCXIWMhEFb7hZx3TFCbiDLcZDg
V42AU9LKsW5+/u4oVY9MeA3kcaWRSJeNfyl/7UKboWhgSaZGSjFnPmaVGHLIEA/E
tIpjeCudNkPp4mpTYziZ5mYxMhzWLeFnMqcIMrTxnnZkEKU1ESzzkr09AkqmHSh/
ohiBwQKBgQCvaDQS7aBS/1+iBrOyMwxARwFirReJHdgEtkBVrB+sasbj1B4Jd3zO
gL6FzxKCKBMIF+sSAjKs0XoRG1K6OPGFg1b5naEq023gObO6aBZqSXhHCajGK790
Xhrvj6BojWkJUnc8T0cocrwhMJrTFC0u00KgAnRNyNYw1vccd5q2uQ==
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,66 @@
![Fichier-Stockage](./img/logo-Fichier-Stockage.png)
URL : HHHHH
# Fichier-Stockage
GGGGG
# Téléchargement, Configuration et Lancement
## Téléchargement de Fichier-Stockage
Saisir la commande pour télécharger la source
```bash
git clone https://git.tips-of-mine.fr/Tips-Of-Mine/Docker.git
```
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
## Modifier la configuration de Fichier-Stockage
Saisir la commande pour vous rendre dans le dossier
```bash
cd AAAAA\Fichier-Stockage
```
Nous éditons le fichier de configuration
```bash
nano .env
```
Nous modifions les variables dont nous avons besoin.
## Lancement de Fichier-Stockage
Pour utiliser Fichier-Stockage tout seul
```bash
docker compose up -d
```
Pour utiliser Fichier-Stockage avec Traefik
```bash
docker compose -f docker-compose-traefik.yml up -d
```
# Utilisation
## Accueil
Ouvrir une page web avec l'url :
Pour une utilisation tout seul
http://10.0.4.29:3000
Pour une utilisation avec Traefik
https://Fichier-Stockage.10.0.4.29.traefik.me`)"
# More info
- more information on the website [Tips-Of-Mine](https://www.tips-of-mine.fr/)
# Buy me a coffe
<a href='https://ko-fi.com/R5R2KNI3N' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://storage.ko-fi.com/cdn/kofi4.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

View File

@ -0,0 +1,22 @@
version: '3'
services:
mirotalksfu:
image: mirotalk/sfu:latest
build:
context: .
dockerfile: Dockerfile
container_name: mirotalksfu
hostname: mirotalksfu
restart: unless-stopped
volumes:
- ./app/src/config.js:/src/app/src/config.js:ro
# These volume is mandatory if server.recording.enabled in the app/src/config.js
# - ./app/rec:/src/app/rec
# These volumes are not mandatory, comment if you want to use it
# - ./app/:/src/app/:ro
# - ./public/:/src/public/:ro
ports:
- '3010:3010/tcp'
- '40000-40100:40000-40100/tcp'
- '40000-40100:40000-40100/udp'

View File

@ -0,0 +1,8 @@
#### NETWORKS
networks:
back_network:
driver: bridge
attachable: true
#### SERVICES
services:

View File

@ -0,0 +1,11 @@
# MiroTalk SFU - Ngrok
### What is the purpose and functionality of Ngrok?
[https://docs.mirotalk.com/ngrok/ngrok/](https://docs.mirotalk.com/ngrok/ngrok/)
### How to exposing MiroTalk SFU with Ngrok?
[https://docs.mirotalk.com/mirotalk-sfu/ngrok/](https://docs.mirotalk.com/mirotalk-sfu/ngrok/)
---

View File

@ -0,0 +1,7 @@
# MiroTalk SFU - Self Hosting
### How can I self-host MiroTalk SFU on my own server?
[https://docs.mirotalk.com/mirotalk-sfu/self-hosting/](https://docs.mirotalk.com/mirotalk-sfu/self-hosting/)
---

View File

@ -0,0 +1,160 @@
#!/bin/bash
# ============================================
# RUN sudo ./install.sh
# ============================================
set -e # Exit immediately if a command exits with a non-zero status
# -------------------------------------------------------
# bash colors for log
# -------------------------------------------------------
black=`tput setaf 0`
red=`tput setaf 1`
green=`tput setaf 2`
yellow=`tput setaf 3`
blue=`tput setaf 4`
magenta=`tput setaf 5`
cyan=`tput setaf 6`
white=`tput setaf 7`
reset=`tput sgr0`
# -------------------------------------------------------
# print log level
# -------------------------------------------------------
function log() {
date_now=`date '+%Y-%m-%d %H:%M:%S'`
case $1 in
debug) echo -e "${date_now} :: ${2}" ;;
warning) echo -e "${date_now} :: ${yellow}${2}${reset}" ;;
error) echo -e "${date_now} :: ${red}${2}${reset}" ;;
*) echo -e "${date_now} :: ${magenta}${1}${reset}" ;;
esac
}
# -------------------------------------------------------
# Check if Linux OS
# -------------------------------------------------------
unamestr=$(uname)
if [[ "$unamestr" != 'Linux' ]]; then
log warning "This install script is supported only on Linux OS"
exit
fi
# -------------------------------------------------------
# Check if run as root
# -------------------------------------------------------
if [ "$EUID" -ne 0 ]; then
log warning "Please run as root: sudo ./install.sh"
exit
fi
# ============================================
# Start the installation...
# ============================================
printf 'Install the dependences (y/n)? '
read answer
if [ "$answer" != "${answer#[Yy]}" ] ;then
log "Update package lists"
apt-get update
log "Install essential build tools: gcc, g++, make"
apt-get install -y build-essential
log "Install Python 3.8 and pip"
apt-get install -y software-properties-common
add-apt-repository -y ppa:deadsnakes/ppa
apt-get update
apt-get install -y python3.8 python3-pip
log "Install Node.js 18.x and npm"
apt install -y curl dirmngr apt-transport-https lsb-release ca-certificates
curl -fsSL https://deb.nodesource.com/setup_18.x | bash -
apt-get install -y nodejs
npm install -g npm@latest
fi
CONFIG=app/src/config.js
if ! [ -f "$CONFIG" ]; then
log "Copy the configuration file"
cp app/src/config.template.js $CONFIG
fi
printf 'Use docker (y/n)? '
read answer
if [ "$answer" != "${answer#[Yy]}" ] ;then
log "Install Docker and Docker Compose"
sudo apt install -y docker.io
sudo apt install -y docker-compose
log "Add the current user to the docker group"
usermod -aG docker $USER
YAML=docker-compose.yml
if ! [ -f "$YAML" ]; then
log "Copy Docker compose yaml file"
cp docker-compose.template.yml $YAML
fi
printf 'Use official docker image (y/n)? '
read answer
if [ "$answer" != "${answer#[Yy]}" ] ;then
log "Get latest official image from Docker Hub"
docker pull mirotalk/sfu:latest
else
log "Build image from source"
docker-compose build
log "Remove old and unused docker images"
docker images |grep '<none>' |awk '{print $3}' |xargs docker rmi
fi
log "Start containers"
docker-compose up #-d
else
log "Install dependencies"
npm install
log "Start the server"
npm start
fi

View File

@ -0,0 +1,72 @@
{
"name": "mirotalksfu",
"version": "1.4.20",
"description": "WebRTC SFU browser-based video calls",
"main": "Server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node app/src/Server.js",
"start-dev": "nodemon app/src/Server.js",
"debug": "DEBUG='mediasoup*' node app/src/Server.js",
"compile": "npx browserify public/sfu/MediasoupClientCompile.js -o public/sfu/MediasoupClient.js",
"lint": "npx prettier --write .",
"docker-build": "docker build --tag mirotalk/sfu:latest .",
"docker-rmi": "docker images |grep '<none>' |awk '{print $3}' |xargs docker rmi",
"docker-push": "docker push mirotalk/sfu:latest",
"docker-pull": "docker pull mirotalk/sfu:latest",
"docker-rmc": "docker container rm mirotalksfu --force",
"docker-run": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./app/src/config.js:/src/app/src/config.js:ro --name mirotalksfu mirotalk/sfu:latest",
"docker-run-vm": "docker run -d -p 40000-40100:40000-40100 -p 3010:3010 -v ./app/:/src/app/:ro -v ./public/:/src/public/:ro --name mirotalksfu mirotalk/sfu:latest",
"docker-start": "docker start mirotalksfu",
"docker-stop": "docker stop mirotalksfu"
},
"repository": {
"type": "git",
"url": "git+https://github.com/miroslavpejic85/mirotalksfu"
},
"keywords": [
"webrtc",
"socket.io",
"broadcasting",
"sfu",
"nodejs",
"video",
"audio",
"openai",
"chatgpt"
],
"author": "Miroslav Pejic",
"license": "AGPL-3.0",
"engines": {
"node": ">=18"
},
"dependencies": {
"@sentry/integrations": "7.110.1",
"@sentry/node": "7.110.1",
"axios": "^1.6.8",
"body-parser": "1.20.2",
"colors": "1.4.0",
"compression": "1.7.4",
"cors": "2.8.5",
"crypto-js": "4.2.0",
"express": "4.19.2",
"httpolyglot": "0.1.2",
"jsonwebtoken": "^9.0.2",
"mediasoup": "3.14.1",
"mediasoup-client": "3.7.7",
"ngrok": "^5.0.0-beta.2",
"nodemailer": "^6.9.13",
"openai": "^4.37.0",
"qs": "6.12.1",
"socket.io": "4.7.5",
"swagger-ui-express": "5.0.0",
"uuid": "9.0.1",
"xss": "^1.0.15",
"yamljs": "0.3.0"
},
"devDependencies": {
"node-fetch": "^3.3.2",
"nodemon": "^3.1.0",
"prettier": "3.2.5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

View File

@ -0,0 +1,365 @@
.container {
padding: 2px !important;
transition:
width 0.5s,
background 2s ease !important;
/* overflow-y: auto;
overflow-x: hidden; */
}
/* Chat Room */
.chat-container {
z-index: 5;
position: relative;
width: var(--msger-width);
height: var(--msger-height);
min-width: var(--msger-width);
min-height: var(--msger-height);
padding: 3px;
background: var(--msger-bg);
border: var(--border);
border-radius: 10px;
box-shadow: var(--box-shadow);
transition: background 1s;
/* border: 1px solid lime; */
}
/* Chat app container */
.chat-app {
position: absolute;
width: 100%;
height: 100%;
border-radius: 10px;
overflow-y: auto;
overflow-x: hidden;
/* border: 1px solid lime; */
}
/* Chat app people container */
.chat-app .people-list {
z-index: 6;
position: absolute;
padding: 20px;
top: 0;
left: 0;
width: 300px;
height: 99%;
background: var(--msger-bg);
overflow-y: auto;
overflow-x: hidden;
/* border: 1px solid lime; */
}
/* Chat app people list container */
.people-list {
-moz-transition: 0.5s;
-o-transition: 0.5s;
-webkit-transition: 0.5s;
transition: 0.5s;
/* border: 1px solid lime; */
}
.people-list .chat-list li {
padding: 10px 15px;
list-style: none;
border-radius: 3px;
}
.people-list .chat-list li:hover {
background: rgba(0, 0, 0, 0.2);
cursor: pointer;
}
.people-list .chat-list li.active {
background: rgba(0, 0, 0, 0.2);
}
.people-list .chat-list li .name {
color: #fff;
font-size: 15px;
}
.people-list .chat-list img {
width: 45px;
border-radius: 50%;
}
.people-list img {
float: left;
border-radius: 50%;
}
.people-list .about {
float: left;
padding-left: 8px;
}
.people-list .about-buttons {
margin-top: 35px;
width: auto;
/* display: inline-flex; */
}
.people-list .status {
color: #999;
font-size: 13px;
}
/* Chat app people list container */
.chat-app .chat-list {
height: auto;
/* border: 1px solid lime; */
}
/* Chat app container */
.chat-app .chat {
position: relative;
margin-left: 300px;
border-left: var(--border);
border-radius: 10px;
/* border: 1px solid lime; */
}
/* Chat header */
.chat .chat-header {
padding: 15px 20px; /* top, right, bottom, left */
border-bottom: var(--border);
height: 70px;
max-height: 70px;
cursor: move;
/* overflow-x: auto; */
/* border: 1px solid lime; */
}
.all-participants-img {
border: var(--border);
width: 40px;
margin-right: 5px;
cursor: pointer;
}
.all-participants-img:hover {
background-color: lime;
transition: all 0.3s ease-in-out;
}
.chat .chat-header img {
float: left;
border-radius: 40px;
width: 40px;
}
.chat .chat-header .chat-about {
float: left;
padding-left: 10px;
color: #fff;
}
.chat .chat-header .status {
color: #999;
font-size: 13px;
}
.chat .chat-header .chat-option-buttons {
position: absolute;
display: inline-flex;
top: 20px;
right: 20px;
z-index: 5;
}
/* Chat history */
.chat .chat-history {
padding: 20px;
height: calc(100vh - 210px);
min-height: 490px;
max-height: 490px;
border-bottom: var(--border);
overflow-y: auto;
overflow-x: hidden;
/* border: 1px solid lime; */
}
.chat .chat-history ul {
padding: 0;
}
.chat .chat-history ul li {
list-style: none;
margin-bottom: 30px;
}
.chat .chat-history ul li:last-child {
margin-bottom: 0px;
}
.chat .chat-history .message-data {
margin-bottom: 15px;
}
.chat .chat-history .message-data img {
border-radius: 40px;
width: 40px;
}
.chat .chat-history .message-data-time {
color: #eeeeee;
padding-left: 6px;
}
/* chat message bubble */
.chat .chat-history .message {
color: #fff;
padding: 18px 20px;
line-height: 26px;
font-size: 16px;
max-width: 100%;
border-radius: 7px;
display: inline-block;
position: relative;
/* border: 1px solid lime; */
}
.chat .chat-history .message:after {
bottom: 100%;
left: 7%;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: grey;
border-width: 10px;
margin-left: -10px;
}
.chat .chat-history .my-message {
background: var(--right-msg-bg);
}
.chat .chat-history .my-message:after {
bottom: 100%;
left: 30px;
border: solid transparent;
content: ' ';
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-bottom-color: #999;
border-width: 10px;
margin-left: -10px;
}
.chat .chat-history .other-message {
background: var(--left-msg-bg);
text-align: left;
}
.chat .chat-history .other-message:after {
border-bottom-color: grey;
left: 93%;
}
/* Chat message */
.chat .chat-message {
padding: 20px;
max-height: 140px;
overflow-y: auto;
overflow-x: hidden;
/* border: 1px solid lime; */
}
/* Chat emoji */
.chatEmojiPicker {
z-index: 0;
position: absolute;
left: 5px;
bottom: 135px;
}
/* status */
.online,
.offline,
.me {
margin-right: 2px;
font-size: 8px;
vertical-align: middle;
}
.online {
color: #86c541;
}
.offline {
color: #e47297;
}
.me {
color: #1d8ecd;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.inline {
display: inline-flex;
}
.absolute {
position: absolute;
}
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: ' ';
clear: both;
height: 0;
}
/** common **/
.mt5 {
margin-top: 5px;
}
.mt10 {
margin-top: 10px;
}
.mr5 {
margin-right: 5px;
}
.ml5 {
margin-left: 5px;
}
.ml10 {
margin-left: 10px;
}
/* Chat search and input message */
textarea,
input[type='text'] {
background: var(--msger-bg) !important;
color: #fff !important;
transition: height 0.5s !important;
}
.form-check-input {
cursor: pointer;
}
.hr {
border: 0;
display: block;
height: 1px;
background: #000000;
margin-top: 0px;
margin-bottom: 10px;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,47 @@
#snow-container {
top: 0;
width: 100%;
height: 100vh;
position: absolute;
overflow: hidden;
transition: opacity 500ms;
}
.snow {
position: absolute;
animation:
fall ease-in infinite,
sway ease-in-out infinite;
color: skyblue;
}
@keyframes fall {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
top: 100vh;
opacity: 1;
}
}
@keyframes sway {
0% {
margin-left: 0;
}
25% {
margin-left: 50px;
}
50% {
margin-left: -50px;
}
75% {
margin-left: 50px;
}
100% {
margin-left: 0;
}
}

View File

@ -0,0 +1,244 @@
/*--------------------------------------------------------------
# Video grid
--------------------------------------------------------------*/
#videoMediaContainer {
z-index: 1;
position: absolute;
display: flex;
top: 0;
width: 100%;
height: 100%;
border-radius: 5px;
align-content: center;
flex-wrap: wrap;
align-items: center;
justify-content: center;
vertical-align: middle;
overflow: hidden;
/* border: 3px solid blue; */
}
#videoPinMediaContainer {
z-index: 1;
position: absolute;
display: none;
top: 0;
left: 0;
width: 75%;
height: 100%;
border-radius: 5px;
box-shadow: var(--box-shadow);
overflow: hidden;
/* animation: show 0.4s ease; */
/* border: 3px solid lime; */
}
.Camera {
position: relative;
vertical-align: middle;
align-self: center;
overflow: hidden;
display: inline-block;
background: transparent;
border-radius: 10px;
border: var(--border);
box-shadow: var(--box-shadow);
animation: show 0.4s ease;
}
/* .Camera:hover {
border: 3px solid rgb(113, 157, 239);
} */
#videoMediaContainer i {
position: absolute;
display: none;
top: 0;
color: rgb(0, 255, 71);
font-size: 14px;
align-items: center;
padding: 5px;
margin: 5px;
width: auto;
height: 25px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.4);
}
.videoAvatarImage {
z-index: 7;
position: absolute;
display: none;
width: var(--vmi-wh);
height: var(--vmi-wh);
border-radius: 50%;
}
.audio {
position: absolute;
right: 0;
color: #fff;
margin: 5px;
width: auto;
height: 25px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.1);
}
.username {
z-index: 1;
position: absolute;
right: 0;
bottom: 0;
color: #fff;
font-size: 10px;
display: flex;
align-items: center;
padding: 5px;
margin: 5px;
width: auto;
height: 25px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.1);
}
.fscreen {
position: absolute;
right: 0;
bottom: 0px;
color: #fff;
margin: 5px;
width: auto;
height: 25px;
border-radius: 5px;
background: rgba(0, 0, 0, 0.1);
}
.videoMenuBar {
z-index: 2;
position: absolute;
display: inline;
top: 0;
left: 0;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
font-size: small;
font-weight: bold;
text-align: center;
width: 100%;
cursor: default;
overflow: hidden;
}
.videoMenuBar input,
.videoMenuBar button {
float: right;
color: #fff;
background: transparent;
border-radius: 5px;
display: inline;
border: none;
}
.videoMenuBar button:hover {
color: grey;
transition: all 0.3s ease-in-out;
}
.expand-video-content {
position: relative;
display: none;
float: right;
width: auto;
}
.expand-video:hover .expand-video-content {
display: inline-flex;
}
#videoMediaContainer video {
position: absolute;
margin-left: auto;
margin-right: auto;
}
.videoCircle {
position: absolute;
width: var(--vmi-wh);
height: var(--vmi-wh);
border-radius: 50%;
/* center */
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
.videoDefault {
position: absolute;
width: 100%;
height: 100%;
border-radius: '10px';
}
video {
width: 100%;
height: 100%;
object-fit: var(--videoObjFit);
border-radius: 10px;
cursor: pointer;
}
video:hover {
opacity: 0.9;
}
video:fullscreen {
object-fit: contain;
opacity: 1;
}
.mirror {
-webkit-transform: rotateY(180deg);
-moz-transform: rotateY(180deg);
transform: rotateY(180deg);
}
.blur {
-webkit-filter: blur(5px);
-moz-filter: blur(5px);
-o-filter: blur(5px);
-ms-filter: blur(5px);
filter: blur(5px);
}
input[type='range'] {
/* display: none; */
color: #fff;
width: 50px;
cursor: pointer;
}
@keyframes show {
0% {
opacity: 0;
transform: scale(0.4) translateY(20px);
}
100% {
opacity: 1;
transform: scale(1) translateY(0);
}
}
@media screen and (max-width: 600px) {
.username {
font-size: 12px;
}
}
@media screen and (max-width: 500px) {
.username {
font-size: 10px;
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -0,0 +1,590 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="1516px" height="1313px" viewBox="-0.5 -0.5 1516 1313">
<defs>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-e3e3e3-1-9c9c9c-1-s-0">
<stop offset="0%" style="stop-color:#E3E3E3"/>
<stop offset="100%" style="stop-color:#9C9C9C"/>
</linearGradient>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-ffffff-1-e3e3e3-1-s-0">
<stop offset="0%" style="stop-color:#ffffff"/>
<stop offset="100%" style="stop-color:#E3E3E3"/>
</linearGradient>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-ea6b66-1-ff3333-1-s-0">
<stop offset="0%" style="stop-color:#EA6B66"/>
<stop offset="100%" style="stop-color:#FF3333"/>
</linearGradient>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-66cc00-1-4d9900-1-s-0">
<stop offset="0%" style="stop-color:#66CC00"/>
<stop offset="100%" style="stop-color:#4D9900"/>
</linearGradient>
<linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-70a9ff-1-2e89ff-1-s-0">
<stop offset="0%" style="stop-color:#70A9FF"/>
<stop offset="100%" style="stop-color:#2E89FF"/>
</linearGradient>
</defs>
<g>
<rect x="275" y="21" width="980" height="1240" rx="19.6" ry="19.6" fill="url(#mx-gradient-e3e3e3-1-9c9c9c-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(743.5,1270.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="42" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; width: 42px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Host</div>
</div>
</foreignObject>
<text x="21" y="21" fill="#787878" text-anchor="middle" font-size="20px" font-family="Helvetica">Host</text>
</switch>
</g>
<rect x="325" y="71" width="883" height="640" rx="19.2" ry="19.2" fill="url(#mx-gradient-ffffff-1-e3e3e3-1-s-0)" stroke="#616161" stroke-width="2" pointer-events="none"/>
<g transform="translate(725.5,721.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="80" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(97, 97, 97); line-height: 1.2; vertical-align: top; width: 82px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Worker 1</div>
</div>
</foreignObject>
<text x="40" y="21" fill="#616161" text-anchor="middle" font-size="20px" font-family="Helvetica">Worker 1</text>
</switch>
</g>
<rect x="357.5" y="121" width="150" height="150" rx="22.5" ry="22.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(369.5,278.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="125" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 125px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="63" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 508 196 L 709.76 196" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 717.76 196 L 709.76 200 L 709.76 192 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 382.5 136 L 472.5 136 L 472.5 136 L 482.5 151 L 472.5 166 L 472.5 166 L 382.5 166 L 392.5 151 Z" fill="url(#mx-gradient-ea6b66-1-ff3333-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(377.5,172.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="110" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(255, 51, 51); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Audio Producer</div>
</div>
</foreignObject>
<text x="55" y="17" fill="#FF3333" text-anchor="middle" font-size="16px" font-family="Helvetica">Audio Producer</text>
</switch>
</g>
<path d="M 382.5 206 L 472.5 206 L 472.5 206 L 482.5 221 L 472.5 236 L 472.5 236 L 382.5 236 L 392.5 221 Z" fill="url(#mx-gradient-ea6b66-1-ff3333-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(377.5,242.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="110" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(255, 51, 51); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Producer</div>
</div>
</foreignObject>
<text x="55" y="17" fill="#FF3333" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Producer</text>
</switch>
</g>
<path d="M 820 196 L 1004.85 172.3" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1012.78 171.28 L 1005.36 176.27 L 1004.34 168.33 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 820 196 L 1011.57 328.19" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1018.16 332.73 L 1009.3 331.48 L 1013.85 324.89 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<rect x="357.5" y="366" width="150" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(367.5,463.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="129" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 129px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PlainRtpTransport</div>
</div>
</foreignObject>
<text x="65" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PlainRtpTransport</text>
</switch>
</g>
<path d="M 382.5 391 L 472.5 391 L 472.5 391 L 482.5 406 L 472.5 421 L 472.5 421 L 382.5 421 L 392.5 406 Z" fill="url(#mx-gradient-ea6b66-1-ff3333-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(377.5,427.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="110" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(255, 51, 51); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Producer</div>
</div>
</foreignObject>
<text x="55" y="17" fill="#FF3333" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Producer</text>
</switch>
</g>
<path d="M 483 406 L 709.76 406" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 717.76 406 L 709.76 410 L 709.76 402 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<rect x="555" y="548.5" width="155" height="130" rx="19.5" ry="19.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(668.5,613.5)rotate(-90,49.5,0)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="99" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PipeTransport</div>
</div>
</foreignObject>
<text x="50" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PipeTransport</text>
</switch>
</g>
<path d="M 582 598.5 L 672 598.5 L 672 598.5 L 682 613.5 L 672 628.5 L 672 628.5 L 582 628.5 L 592 613.5 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" transform="rotate(90,632,613.5)" pointer-events="none"/>
<g transform="translate(602.5,604.5)rotate(-90,59.5,8.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<path d="M 770 456 L 641.47 543.25" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 634.85 547.74 L 639.22 539.94 L 643.72 546.56 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 114 196 L 347.26 196" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 355.26 196 L 347.26 200 L 347.26 192 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="217" y="189" width="39" height="17" stroke-width="0"/>
<text x="235" y="200.5">SRTP</text>
</g>
<path d="M 1175 171 L 1414.76 169.08" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1422.76 169.02 L 1414.8 173.08 L 1414.73 165.08 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="1280" y="163" width="39" height="17" stroke-width="0"/>
<text x="1299" y="174.5">SRTP</text>
</g>
<path d="M 89.53 210.86 C 90.09 210.86 90.48 210.64 90.37 210.48 L 89.26 208.86 C 89.12 208.61 88.92 208.57 88.59 208.57 L 81.41 208.57 C 81.06 208.57 80.88 208.62 80.71 208.87 L 79.68 210.47 C 79.48 210.72 80.08 210.86 80.51 210.86 Z M 106.32 205.61 L 106.32 181.96 L 63.65 181.96 L 63.65 205.61 Z M 59 213 C 57.87 212.99 56.85 212.62 56.42 212.03 C 56 211.46 56.17 210.89 56.53 210.5 L 60.66 206.35 L 60.66 181.83 C 60.66 180.6 61.78 179 63.63 179 L 106.33 179 C 107.77 179 109.31 180.14 109.31 181.99 L 109.31 206.35 L 113.48 210.54 C 113.83 210.93 114 211.45 113.58 212.01 C 113.03 212.72 111.99 212.95 111.02 213 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(23.5,219.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="122" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br />
(mic/webcam on)<br />
</div>
</div>
</foreignObject>
<text x="61" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">Participant&lt;br&gt;(mic/webcam on)&lt;br&gt;</text>
</switch>
</g>
<path d="M 1443.63 187.3 L 1484.45 187.3 L 1484.45 167.14 L 1443.63 167.14 Z M 1442.65 190.29 C 1441.41 190.29 1440.08 189.41 1440.08 188.09 L 1440.08 166.3 C 1440.08 165.24 1441.14 164.15 1442.69 164.15 L 1485.4 164.15 C 1486.96 164.15 1488 165.27 1488 166.29 L 1488 188 C 1488 189.32 1486.89 190.29 1485.27 190.29 L 1469.72 190.29 C 1469.41 191.2 1469.11 192.17 1469.05 192.99 C 1468.92 194.69 1470.19 195.17 1472.47 195.44 C 1473.99 195.6 1475.34 195.63 1476.78 195.66 C 1477.66 195.66 1478.5 196.2 1478.5 197.08 C 1478.5 197.92 1477.78 198.5 1476.8 198.5 L 1451.34 198.5 C 1450.44 198.5 1449.67 197.88 1449.67 197.08 C 1449.67 196.36 1450.34 195.66 1451.34 195.66 C 1453.02 195.61 1454.73 195.61 1456.49 195.32 C 1458.19 195.05 1459.23 194.52 1459.04 192.9 C 1458.97 192.17 1458.67 191.2 1458.36 190.29 Z M 1431.21 151.46 L 1450.73 151.46 L 1450.73 149.22 L 1431.21 149.22 Z M 1425 194.77 L 1425 145.43 C 1425 143.74 1426.81 142.5 1428.4 142.5 L 1453.5 142.5 C 1455.25 142.5 1456.94 143.82 1456.94 145.43 L 1456.94 161.91 L 1442.56 161.91 C 1439.86 161.91 1437.42 164.02 1437.42 166.25 L 1437.42 179.83 L 1431.21 179.83 L 1431.21 182.07 L 1437.42 182.07 L 1437.42 186.55 L 1431.21 186.55 L 1431.21 188.79 L 1437.49 188.79 C 1438 191.15 1440.47 192.53 1442.55 192.53 L 1456.23 192.53 C 1456.23 192.71 1456.33 192.9 1456.32 193.04 C 1455.42 193.29 1453.39 193.42 1451.68 193.42 C 1450.92 193.39 1450.27 193.47 1449.61 193.7 C 1448.96 193.92 1448.39 194.28 1447.9 194.77 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(1418.5,205.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="76" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br />
(viewer)<br />
</div>
</div>
</foreignObject>
<text x="38" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 1175 334 L 1423.76 334.44" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1431.76 334.46 L 1423.76 338.44 L 1423.77 330.44 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="1290" y="327" width="30" height="17" stroke-width="0"/>
<text x="1304" y="338.73">RTP</text>
</g>
<path d="M 1461.13 335.37 C 1461.51 334.99 1461.76 334.05 1461.03 333.31 L 1447.88 320.84 C 1446.97 320.1 1445.62 319.95 1444.53 320.91 C 1443.45 322.03 1443.76 323.6 1444.56 324.37 L 1455.04 334.31 L 1444.68 343.47 C 1443.82 344.27 1443.46 345.56 1444.14 346.7 C 1445.05 348.16 1446.84 348.07 1447.82 347.23 Z M 1468.15 347.92 C 1468.97 347.92 1469.98 347.24 1469.98 346.03 C 1469.98 345.03 1469.22 344.15 1468.13 344.15 L 1457.66 344.15 C 1456.99 344.15 1455.86 344.74 1455.86 345.99 C 1455.86 347.13 1456.74 347.92 1457.69 347.92 Z M 1436.88 357 C 1435.4 357 1434 355.74 1434 354.13 L 1434 314.82 C 1434 313.19 1435.53 312 1436.79 312 L 1476.05 312 C 1477.64 312 1479 313.26 1479 314.91 L 1479 354.11 C 1479 355.9 1477.5 357 1476.21 357 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(1417.5,363.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="77" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">FFmpeg<br />
(recording)<br />
</div>
</div>
</foreignObject>
<text x="39" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 89.42 411.87 C 89.8 411.49 90.05 410.55 89.32 409.81 L 76.18 397.34 C 75.27 396.6 73.91 396.45 72.82 397.41 C 71.74 398.53 72.05 400.1 72.85 400.87 L 83.33 410.81 L 72.97 419.97 C 72.11 420.77 71.75 422.06 72.43 423.2 C 73.34 424.66 75.14 424.57 76.12 423.73 Z M 96.44 424.42 C 97.27 424.42 98.28 423.74 98.28 422.53 C 98.28 421.53 97.52 420.65 96.42 420.65 L 85.96 420.65 C 85.28 420.65 84.15 421.24 84.15 422.49 C 84.15 423.63 85.03 424.42 85.99 424.42 Z M 65.17 433.5 C 63.7 433.5 62.29 432.24 62.29 430.63 L 62.29 391.32 C 62.29 389.69 63.83 388.5 65.09 388.5 L 104.35 388.5 C 105.93 388.5 107.29 389.76 107.29 391.41 L 107.29 430.61 C 107.29 432.4 105.79 433.5 104.5 433.5 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(18.5,440.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="131" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">GStreamer<br />
(mp4 broadcaster)<br />
</div>
</div>
</foreignObject>
<text x="66" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">GStreamer&lt;br&gt;(mp4 broadcaster)&lt;br&gt;</text>
</switch>
</g>
<path d="M 107.29 411 L 347.76 411" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 355.76 411 L 347.76 415 L 347.76 407 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="217" y="404" width="30" height="17" stroke-width="0"/>
<text x="231.29" y="415.5">RTP</text>
</g>
<path d="M 780 382 L 794 382 L 794 396 L 800 396 L 800 386 L 820 406 L 800 426 L 800 416 L 794 416 L 794 430 L 780 430 L 780 436 L 790 436 L 770 456 L 750 436 L 760 436 L 760 430 L 746 430 L 746 416 L 740 416 L 740 426 L 720 406 L 740 386 L 740 396 L 746 396 L 746 382 L 760 382 L 760 376 L 750 376 L 770 356 L 790 376 L 780 376 Z" fill="url(#mx-gradient-70a9ff-1-2e89ff-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(831.5,394.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="77" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(46, 137, 255); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Router 2</div>
</div>
</foreignObject>
<text x="39" y="21" fill="#2E89FF" text-anchor="middle" font-size="20px" font-family="Helvetica">Router 2</text>
</switch>
</g>
<path d="M 780 172 L 794 172 L 794 186 L 800 186 L 800 176 L 820 196 L 800 216 L 800 206 L 794 206 L 794 220 L 780 220 L 780 226 L 790 226 L 770 246 L 750 226 L 760 226 L 760 220 L 746 220 L 746 206 L 740 206 L 740 216 L 720 196 L 740 176 L 740 186 L 746 186 L 746 172 L 760 172 L 760 166 L 750 166 L 770 146 L 790 166 L 780 166 Z" fill="url(#mx-gradient-70a9ff-1-2e89ff-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(731.5,252.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="77" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(46, 137, 255); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Router 1</div>
</div>
</foreignObject>
<text x="39" y="21" fill="#2E89FF" text-anchor="middle" font-size="20px" font-family="Helvetica">Router 1</text>
</switch>
</g>
<rect x="325" y="761" width="425" height="450" rx="12.75" ry="12.75" fill="url(#mx-gradient-ffffff-1-e3e3e3-1-s-0)" stroke="#616161" stroke-width="2" pointer-events="none"/>
<g transform="translate(496.5,1221.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="80" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(97, 97, 97); line-height: 1.2; vertical-align: top; width: 82px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Worker 2</div>
</div>
</foreignObject>
<text x="40" y="21" fill="#616161" text-anchor="middle" font-size="20px" font-family="Helvetica">Worker 2</text>
</switch>
</g>
<rect x="819.5" y="548.5" width="155" height="130" rx="19.5" ry="19.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(932.5,613.5)rotate(-90,49.5,0)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="99" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PipeTransport</div>
</div>
</foreignObject>
<text x="50" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PipeTransport</text>
</switch>
</g>
<path d="M 846.5 598.5 L 936.5 598.5 L 936.5 598.5 L 946.5 613.5 L 936.5 628.5 L 936.5 628.5 L 846.5 628.5 L 856.5 613.5 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" transform="rotate(90,896.5,613.5)" pointer-events="none"/>
<g transform="translate(866.5,604.5)rotate(-90,59.5,8.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<path d="M 770 456 L 888.74 542.95" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 895.2 547.68 L 886.38 546.18 L 891.1 539.73 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 642 1022 L 656 1022 L 656 1036 L 662 1036 L 662 1026 L 682 1046 L 662 1066 L 662 1056 L 656 1056 L 656 1070 L 642 1070 L 642 1076 L 652 1076 L 632 1096 L 612 1076 L 622 1076 L 622 1070 L 608 1070 L 608 1056 L 602 1056 L 602 1066 L 582 1046 L 602 1026 L 602 1036 L 608 1036 L 608 1022 L 622 1022 L 622 1016 L 612 1016 L 632 996 L 652 1016 L 642 1016 Z" fill="url(#mx-gradient-70a9ff-1-2e89ff-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(593.5,1102.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="77" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(46, 137, 255); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Router 3</div>
</div>
</foreignObject>
<text x="39" y="21" fill="#2E89FF" text-anchor="middle" font-size="20px" font-family="Helvetica">Router 3</text>
</switch>
</g>
<rect x="554.5" y="791" width="155" height="130" rx="19.5" ry="19.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(667.5,856.5)rotate(-90,49.5,0)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="99" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PipeTransport</div>
</div>
</foreignObject>
<text x="50" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PipeTransport</text>
</switch>
</g>
<path d="M 582 841 L 672 841 L 672 841 L 682 856 L 672 871 L 672 871 L 582 871 L 592 856 Z" fill="url(#mx-gradient-ea6b66-1-ff3333-1-s-0)" stroke="none" transform="rotate(90,632,856)" pointer-events="none"/>
<g transform="translate(606.5,847.5)rotate(-90,55,8.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="110" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(255, 51, 51); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Producer</div>
</div>
</foreignObject>
<text x="55" y="17" fill="#FF3333" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Producer</text>
</switch>
</g>
<rect x="785" y="761" width="425" height="450" rx="12.75" ry="12.75" fill="url(#mx-gradient-ffffff-1-e3e3e3-1-s-0)" stroke="#616161" stroke-width="2" pointer-events="none"/>
<g transform="translate(956.5,1221.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="80" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(97, 97, 97); line-height: 1.2; vertical-align: top; width: 82px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Worker 3</div>
</div>
</foreignObject>
<text x="40" y="21" fill="#616161" text-anchor="middle" font-size="20px" font-family="Helvetica">Worker 3</text>
</switch>
</g>
<rect x="824.5" y="791" width="150" height="130" rx="19.5" ry="19.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(932.5,856.5)rotate(-90,49.5,0)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="99" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 101px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PipeTransport</div>
</div>
</foreignObject>
<text x="50" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PipeTransport</text>
</switch>
</g>
<path d="M 847 841 L 937 841 L 937 841 L 947 856 L 937 871 L 937 871 L 847 871 L 857 856 Z" fill="url(#mx-gradient-ea6b66-1-ff3333-1-s-0)" stroke="none" transform="rotate(90,897,856)" pointer-events="none"/>
<g transform="translate(871.5,847.5)rotate(-90,55,8.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="110" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(255, 51, 51); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Producer</div>
</div>
</foreignObject>
<text x="55" y="17" fill="#FF3333" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Producer</text>
</switch>
</g>
<path d="M 907.5 1022 L 921.5 1022 L 921.5 1036 L 927.5 1036 L 927.5 1026 L 947.5 1046 L 927.5 1066 L 927.5 1056 L 921.5 1056 L 921.5 1070 L 907.5 1070 L 907.5 1076 L 917.5 1076 L 897.5 1096 L 877.5 1076 L 887.5 1076 L 887.5 1070 L 873.5 1070 L 873.5 1056 L 867.5 1056 L 867.5 1066 L 847.5 1046 L 867.5 1026 L 867.5 1036 L 873.5 1036 L 873.5 1022 L 887.5 1022 L 887.5 1016 L 877.5 1016 L 897.5 996 L 917.5 1016 L 907.5 1016 Z" fill="url(#mx-gradient-70a9ff-1-2e89ff-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(858.5,1102.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="77" height="22" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 20px; font-family: Helvetica; color: rgb(46, 137, 255); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Router 4</div>
</div>
</foreignObject>
<text x="39" y="21" fill="#2E89FF" text-anchor="middle" font-size="20px" font-family="Helvetica">Router 4</text>
</switch>
</g>
<path d="M 633 679 L 632.09 780.76" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 632.02 788.76 L 628.09 780.73 L 636.09 780.8 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="618" y="727" width="30" height="17" stroke-width="0"/>
<text x="631.5" y="739.5">RTP</text>
</g>
<path d="M 897 679 L 899.73 780.77" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 899.94 788.76 L 895.73 780.87 L 903.72 780.66 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="883" y="727" width="30" height="17" stroke-width="0"/>
<text x="897.5" y="739.48">RTP</text>
</g>
<path d="M 632 921 L 632 985.76" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 632 993.76 L 628 985.76 L 636 985.76 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 900 921 L 898.27 985.77" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 898.06 993.76 L 894.27 985.66 L 902.27 985.87 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1443.63 992.8 L 1484.45 992.8 L 1484.45 972.64 L 1443.63 972.64 Z M 1442.65 995.79 C 1441.41 995.79 1440.08 994.91 1440.08 993.59 L 1440.08 971.8 C 1440.08 970.74 1441.14 969.65 1442.69 969.65 L 1485.4 969.65 C 1486.96 969.65 1488 970.77 1488 971.79 L 1488 993.5 C 1488 994.82 1486.89 995.79 1485.27 995.79 L 1469.72 995.79 C 1469.41 996.7 1469.11 997.67 1469.05 998.49 C 1468.92 1000.19 1470.19 1000.67 1472.47 1000.94 C 1473.99 1001.1 1475.34 1001.13 1476.78 1001.16 C 1477.66 1001.16 1478.5 1001.7 1478.5 1002.58 C 1478.5 1003.42 1477.78 1004 1476.8 1004 L 1451.34 1004 C 1450.44 1004 1449.67 1003.38 1449.67 1002.58 C 1449.67 1001.86 1450.34 1001.16 1451.34 1001.16 C 1453.02 1001.11 1454.73 1001.11 1456.49 1000.82 C 1458.19 1000.55 1459.23 1000.02 1459.04 998.4 C 1458.97 997.67 1458.67 996.7 1458.36 995.79 Z M 1431.21 956.96 L 1450.73 956.96 L 1450.73 954.72 L 1431.21 954.72 Z M 1425 1000.27 L 1425 950.93 C 1425 949.24 1426.81 948 1428.4 948 L 1453.5 948 C 1455.25 948 1456.94 949.32 1456.94 950.93 L 1456.94 967.41 L 1442.56 967.41 C 1439.86 967.41 1437.42 969.52 1437.42 971.75 L 1437.42 985.33 L 1431.21 985.33 L 1431.21 987.57 L 1437.42 987.57 L 1437.42 992.05 L 1431.21 992.05 L 1431.21 994.29 L 1437.49 994.29 C 1438 996.65 1440.47 998.03 1442.55 998.03 L 1456.23 998.03 C 1456.23 998.21 1456.33 998.4 1456.32 998.54 C 1455.42 998.79 1453.39 998.92 1451.68 998.92 C 1450.92 998.89 1450.27 998.97 1449.61 999.2 C 1448.96 999.42 1448.39 999.78 1447.9 1000.27 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(1418.5,1010.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="76" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br />
(viewer)<br />
</div>
</div>
</foreignObject>
<text x="38" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 1459.2 1098.25 C 1459.4 1098.25 1459.52 1098.06 1459.52 1097.85 C 1459.52 1097.67 1459.38 1097.5 1459.21 1097.5 L 1453.89 1097.5 C 1453.7 1097.5 1453.55 1097.63 1453.55 1097.85 C 1453.56 1098.11 1453.72 1098.25 1453.87 1098.25 Z M 1458.87 1134.63 C 1459.23 1134.63 1459.52 1134.37 1459.52 1134.18 L 1459.52 1133.57 C 1459.52 1133.26 1459.17 1133.08 1458.76 1133.08 L 1454.16 1133.08 C 1453.77 1133.08 1453.48 1133.34 1453.48 1133.54 L 1453.48 1134.14 C 1453.48 1134.36 1453.76 1134.63 1454.14 1134.63 Z M 1446.68 1130.92 L 1466.33 1130.92 L 1466.33 1100.47 L 1446.68 1100.47 Z M 1444.5 1100.49 C 1444.5 1098.29 1445.73 1096.39 1447.81 1095.52 C 1451.39 1094.36 1460.92 1094 1465.33 1095.64 C 1467.28 1096.49 1468.5 1098.19 1468.5 1100.54 L 1468.5 1131.73 C 1468.5 1133.34 1467.59 1135.58 1465.47 1136.5 C 1461.54 1137.97 1450.96 1138 1447.54 1136.41 C 1445.52 1135.46 1444.58 1133.7 1444.5 1131.85 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(1418.5,1144.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="76" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br style="font-size: 16px" />
(viewer)<br style="font-size: 16px" />
</div>
</div>
</foreignObject>
<text x="38" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 1175 976 L 1414.76 976" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1422.76 976 L 1414.76 980 L 1414.76 972 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="1281" y="969" width="39" height="17" stroke-width="0"/>
<text x="1299" y="980.5">SRTP</text>
</g>
<path d="M 1175 1116 L 1434.26 1116" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1442.26 1116 L 1434.26 1120 L 1434.26 1112 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="1291" y="1109" width="39" height="17" stroke-width="0"/>
<text x="1309" y="1120.5">SRTP</text>
</g>
<path d="M 948 1046 L 1012.66 983.14" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1018.4 977.56 L 1015.45 986 L 1009.87 980.27 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 948 1046 L 1012.66 1108.86" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 1018.4 1114.44 L 1009.87 1111.73 L 1015.45 1106 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 582 1046 L 517.34 983.14" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 511.6 977.56 L 520.13 980.27 L 514.55 986 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 582 1046 L 517.34 1108.86" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 511.6 1114.44 L 514.55 1106 L 520.13 1111.73 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 355 976 L 124.24 976" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 116.24 976 L 124.24 972 L 124.24 980 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="215" y="969" width="39" height="17" stroke-width="0"/>
<text x="233" y="980.5">SRTP</text>
</g>
<path d="M 89.53 992.17 C 90.09 992.17 90.48 991.94 90.37 991.75 L 89.26 990 C 89.12 989.73 88.92 989.67 88.59 989.67 L 81.41 989.67 C 81.06 989.67 80.88 989.74 80.71 990.01 L 79.68 991.74 C 79.48 992.02 80.08 992.17 80.51 992.17 Z M 106.32 986.46 L 106.32 960.72 L 63.65 960.72 L 63.65 986.46 Z M 59 994.5 C 57.87 994.49 56.85 994.08 56.42 993.45 C 56 992.83 56.17 992.2 56.53 991.78 L 60.66 987.26 L 60.66 960.58 C 60.66 959.24 61.78 957.5 63.63 957.5 L 106.33 957.5 C 107.77 957.5 109.31 958.74 109.31 960.75 L 109.31 987.26 L 113.48 991.82 C 113.83 992.25 114 992.82 113.58 993.43 C 113.03 994.2 111.99 994.45 111.02 994.5 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(46.5,1001.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="76" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br />
(viewer)<br />
</div>
</div>
</foreignObject>
<text x="38" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<rect x="354.86" y="1071" width="155" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(369.5,1168.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="124" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 126px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="62" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 382.36 1096 L 472.36 1096 L 472.36 1096 L 482.36 1111 L 472.36 1126 L 472.36 1126 L 382.36 1126 L 392.36 1111 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" transform="translate(432.36,0)scale(-1,1)translate(-432.36,0)" pointer-events="none"/>
<g transform="translate(372.5,1132.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<rect x="354.86" y="931" width="155" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(369.5,1028.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="124" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 126px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="62" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 382.36 956 L 472.36 956 L 472.36 956 L 482.36 971 L 472.36 986 L 472.36 986 L 382.36 986 L 392.36 971 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" transform="translate(432.36,0)scale(-1,1)translate(-432.36,0)" pointer-events="none"/>
<g transform="translate(372.5,992.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<rect x="1019.86" y="931" width="155" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(1034.5,1028.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="124" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 126px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="62" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 1047.36 956 L 1137.36 956 L 1137.36 956 L 1147.36 971 L 1137.36 986 L 1137.36 986 L 1047.36 986 L 1057.36 971 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(1037.5,992.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<rect x="1019.86" y="1071" width="155" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(1034.5,1168.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="124" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 126px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="62" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 1047.36 1096 L 1137.36 1096 L 1137.36 1096 L 1147.36 1111 L 1137.36 1126 L 1137.36 1126 L 1047.36 1126 L 1057.36 1111 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(1037.5,1132.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<path d="M 89.53 1127.17 C 90.09 1127.17 90.48 1126.94 90.37 1126.75 L 89.26 1125 C 89.12 1124.73 88.92 1124.67 88.59 1124.67 L 81.41 1124.67 C 81.06 1124.67 80.88 1124.74 80.71 1125.01 L 79.68 1126.74 C 79.48 1127.02 80.08 1127.17 80.51 1127.17 Z M 106.32 1121.46 L 106.32 1095.72 L 63.65 1095.72 L 63.65 1121.46 Z M 59 1129.5 C 57.87 1129.49 56.85 1129.08 56.42 1128.45 C 56 1127.83 56.17 1127.2 56.53 1126.78 L 60.66 1122.26 L 60.66 1095.58 C 60.66 1094.24 61.78 1092.5 63.63 1092.5 L 106.33 1092.5 C 107.77 1092.5 109.31 1093.74 109.31 1095.75 L 109.31 1122.26 L 113.48 1126.82 C 113.83 1127.25 114 1127.82 113.58 1128.43 C 113.03 1129.2 111.99 1129.45 111.02 1129.5 Z" fill="#505050" stroke="none" pointer-events="none"/>
<g transform="translate(46.5,1136.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="76" height="36" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(120, 120, 120); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Participant<br />
(viewer)<br />
</div>
</div>
</foreignObject>
<text x="38" y="26" fill="#787878" text-anchor="middle" font-size="16px" font-family="Helvetica">[Not supported by viewer]</text>
</switch>
</g>
<path d="M 355 1116 L 119.24 1114.08" fill="none" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<path d="M 111.24 1114.02 L 119.27 1110.08 L 119.2 1118.08 Z" fill="#999999" stroke="#999999" stroke-width="2" stroke-miterlimit="10" pointer-events="none"/>
<g fill="#999999" font-family="Helvetica" text-anchor="middle" font-size="14px">
<rect fill="#ffffff" stroke="none" x="213" y="1108" width="39" height="17" stroke-width="0"/>
<text x="231" y="1119.5">SRTP</text>
</g>
<rect x="1015" y="95.5" width="160" height="150" rx="22.5" ry="22.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(1031.5,253.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="125" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 125px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">WebRtcTransport</div>
</div>
</foreignObject>
<text x="63" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">WebRtcTransport</text>
</switch>
</g>
<path d="M 1045 109 L 1135 109 L 1135 109 L 1145 124 L 1135 139 L 1135 139 L 1045 139 L 1055 124 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(1035.5,145.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Audio Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Audio Consumer</text>
</switch>
</g>
<path d="M 1045 184 L 1135 184 L 1135 184 L 1145 199 L 1135 214 L 1135 214 L 1045 214 L 1055 199 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(1035.5,220.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Video Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Video Consumer</text>
</switch>
</g>
<rect x="1020" y="289" width="155" height="90" rx="13.5" ry="13.5" fill="#ffffff" stroke="#666666" stroke-width="2" stroke-dasharray="2 4" pointer-events="none"/>
<g transform="translate(1032.5,386.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="128" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(102, 102, 102); line-height: 1.2; vertical-align: top; width: 130px; white-space: nowrap; overflow-wrap: normal; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">PlainRtpTransport</div>
</div>
</foreignObject>
<text x="64" y="17" fill="#666666" text-anchor="middle" font-size="16px" font-family="Helvetica">PlainRtpTransport</text>
</switch>
</g>
<path d="M 1047.5 314 L 1137.5 314 L 1137.5 314 L 1147.5 329 L 1137.5 344 L 1137.5 344 L 1047.5 344 L 1057.5 329 Z" fill="url(#mx-gradient-66cc00-1-4d9900-1-s-0)" stroke="none" pointer-events="none"/>
<g transform="translate(1037.5,350.5)">
<switch>
<foreignObject style="overflow:visible;" pointer-events="all" width="119" height="17" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility">
<div xmlns="http://www.w3.org/1999/xhtml" style="display: inline-block; font-size: 16px; font-family: Helvetica; color: rgb(77, 153, 0); line-height: 1.2; vertical-align: top; white-space: nowrap; text-align: center;">
<div xmlns="http://www.w3.org/1999/xhtml" style="display:inline-block;text-align:inherit;text-decoration:inherit;">Audio Consumer</div>
</div>
</foreignObject>
<text x="60" y="17" fill="#4D9900" text-anchor="middle" font-size="16px" font-family="Helvetica">Audio Consumer</text>
</switch>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

View File

@ -0,0 +1 @@
<svg width="160" height="187" xmlns="http://www.w3.org/2000/svg"><g fill="#0270D7" fill-rule="evenodd"><path fill-opacity=".24" d="M110.211 0l49.45 89.211-89.21 49.45L21 49.452z"/><path fill-opacity=".16" d="M33.812 171.385l-18.385 15.427L0 168.427 18.385 153z"/></g></svg>

After

Width:  |  Height:  |  Size: 273 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg width="112" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><linearGradient x1="99.316%" y1="21.974%" x2="0%" y2="69.777%" id="b"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="48.131%"/><stop stop-color="#2C3039" offset="100%"/></linearGradient><linearGradient x1="49.892%" y1=".428%" x2="24.856%" y2="100%" id="d"><stop stop-color="#3B404C" offset="0%"/><stop stop-color="#333843" offset="100%"/></linearGradient><path d="M32.833 63.264c-14.9 1.866-25.108-6.384-22.797-18.428 2.31-12.043 16.264-23.319 31.166-25.184C56.103 17.786 66.31 26.036 64 38.08c-2.311 12.043-16.264 23.319-31.167 25.184z" id="c"/></defs><g fill="none" fill-rule="evenodd"><path fill="#0270D7" d="M35.521 6.938L12.04 35.843l48.939 16.136L84.46 23.073z"/><path fill="#02CBB1" d="M20 65.494L35.822 74 36 26.506 20.176 18z"/><g transform="translate(28 31)"><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M1 0h40v40H1z"/><path d="M11.261 54.752l-9.71-18.785C.096 33.152-.389 29.783.327 26.051 2.637 14.01 16.59 2.732 31.492.867 41.776-.42 49.824 3.11 53.065 9.38l9.71 18.785c-3.242-6.27-11.29-9.8-21.573-8.514C26.3 21.518 12.347 32.793 10.036 44.837c-.716 3.732-.23 7.1 1.225 9.915" fill="url(#b)"/><use fill="url(#d)" xlink:href="#c"/></g><path fill="#00BFFB" d="M22.991 85.677l.826-6.614L7.855 84.8l-.825 6.616z"/><path fill-opacity=".32" fill="#00BFFB" d="M106.721 46.737l4.408-2.801-13.699-6.737L93.022 40z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1 @@
<svg width="108" height="100" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><linearGradient x1="17.713%" y1="0%" x2="77.754%" y2="68.424%" id="b"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="100%"/></linearGradient><linearGradient x1="49.892%" y1=".428%" x2="24.856%" y2="100%" id="c"><stop stop-color="#3B404C" offset="0%"/><stop stop-color="#333843" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path fill-opacity=".32" fill="#00BFFB" d="M31.292 84.984l1.313-5.055-14.613 4.415-1.313 5.056z"/><path d="M41.203 74.416L.222 46.516l30.541-21.314 23.908 16.277-11.69 10.84-1.706 22-.072.097z" fill="#0270D7"/><path fill="#00BFFB" d="M103.305 14.443l4.556-4.866-16.287-4.74-4.556 4.867z"/><path fill="#02CBB1" d="M56.9 77.244L54.771 92.89 94.416 77.84l2.128-15.648z"/><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M31.778 38.241h40v40h-40z" transform="translate(.222 -2.241)"/><path d="M88.023 31.352a2.962 2.962 0 0 0-1.296-1.359l-29.464-15.06a2.888 2.888 0 0 0-2.634.001L25.147 30.03a2.972 2.972 0 0 0-1.298 1.361l32.078 16.398 32.096-16.436z" fill="url(#b)" fill-rule="nonzero" transform="rotate(31 66.542 55.443)"/><path d="M43.266 11.908a3.154 3.154 0 0 0-.96.977L23.763 43.707c-.586.974-.605 2.174-.048 3.1l16.423 27.335c.228.379.543.693.917.917l20.09-33.39-17.878-29.76z" fill="#2C3039" fill-rule="nonzero"/><path d="M38.935 50.59v38.956a3.04 3.04 0 0 0 1.35-.3L70.47 74.263a2.994 2.994 0 0 0 1.67-2.697V35.608a2.98 2.98 0 0 0-.342-1.33L38.935 50.59z" fill="url(#c)" transform="rotate(31 66.143 86.153)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1 @@
<svg width="103" height="100" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><linearGradient x1="49.892%" y1=".428%" x2="24.856%" y2="100%" id="b"><stop stop-color="#3B404C" offset="0%"/><stop stop-color="#333843" offset="100%"/></linearGradient><linearGradient x1="17.713%" y1="0%" x2="77.754%" y2="68.424%" id="c"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path d="M48.028 64.452L.086 77.076l3.95-37.033 27.97-7.364c.1 3.076.27 8.387.512 15.934l15.485 15.72.025.119z" fill="#0270D7"/><path fill="#02CBB1" d="M91.767 72.042l2.455-13.396L58.895 70.67 56.44 84.067z"/><path fill="#00BFFB" d="M40.783 98.37l6.378-1.937-11.735-12.248-6.379 1.937z"/><path fill-opacity=".32" fill="#00BFFB" d="M23.783 14.37l6.378-1.937L18.426.185l-6.379 1.937z"/><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M50 43h40v40H50z" transform="translate(-16 -12)"/><path d="M51.759 16.34a.94.94 0 0 1 .922.399l23.35 40.193c.096.151.143.35.143.545-.001.629-.481 1.21-1.067 1.292l-46.872 6.553a.909.909 0 0 1-.527-.081c-.507-.237-.688-.912-.39-1.5L50.84 16.996c.076-.134.183-.262.307-.372.19-.16.403-.255.612-.283" fill="url(#b)" transform="rotate(27 42.978 77.641)"/><path d="M25.338.52c-.01-.006-.022-.01-.033-.017a.893.893 0 0 0-.174-.066c-.01-.002-.022.001-.034-.001a.925.925 0 0 0-.346-.018 1.064 1.064 0 0 0-.315.096l-.004.001c-.06.03-.12.064-.178.105a1.172 1.172 0 0 0-.107.083l-.008.008a1.495 1.495 0 0 0-.295.368L.616 48.344a1.469 1.469 0 0 0-.064.148c-.02.057-.02.113-.033.171-.012.058-.028.116-.033.174-.007.084.002.164.012.246.007.052.006.106.02.156a.99.99 0 0 0 .103.233c.02.037.031.08.056.113a.877.877 0 0 0 .28.25l26.938 14.927a.868.868 0 0 1-.28-.252c-.024-.034-.037-.076-.057-.113-.04-.075-.08-.15-.103-.232-.013-.051-.013-.104-.02-.156-.01-.082-.02-.163-.012-.248.005-.058.021-.115.033-.173.024-.107.046-.216.097-.32L50.78 16.006c.076-.136.18-.265.304-.376.091-.08.19-.142.289-.19a.96.96 0 0 1 .662-.078.95.95 0 0 1 .24.084L25.337.52z" transform="rotate(27 42.028 77.95)" fill="url(#c)" fill-rule="nonzero"/></g></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@ -0,0 +1 @@
<svg width="98" height="100" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><linearGradient x1="49.892%" y1=".428%" x2="24.856%" y2="100%" id="b"><stop stop-color="#3B404C" offset="0%"/><stop stop-color="#333843" offset="100%"/></linearGradient><linearGradient x1="17.713%" y1="0%" x2="77.754%" y2="68.424%" id="c"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path d="M49.028 61.452L1.086 74.076l3.95-37.033 27.97-7.364c.1 3.076.27 8.387.512 15.934l15.485 15.72.025.119z" fill="#0270D7"/><path fill-opacity=".32" fill="#00BFFB" d="M23.725 84.328l-5.66 3.52 14.505 8.794 5.66-3.522z"/><path fill="#02CBB1" d="M89.345 72.466l-1.72-15.697L52.8 80.964l1.72 15.698z"/><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M44 41h40v40H44z" transform="translate(-15 -10)"/><path fill="url(#b)" d="M37.841 71.977L0 54.169 37.231 0z" transform="rotate(-36 55.277 18.264)"/><path fill="url(#c)" fill-rule="nonzero" transform="scale(-1 1) rotate(36 .538 -153.515)" d="M73.788 71.977L37.229 54 74.4 0z"/><path fill-opacity=".32" fill="#00BFFB" d="M58.57 10.221l-1.048 5.117 14.362-5.174 1.047-5.118z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
<svg width="98" height="100" xmlns="http://www.w3.org/2000/svg"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><linearGradient x1="7.786%" y1="0%" x2="90.938%" y2="49.407%" id="b"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M38 48h40v40H38z" transform="translate(-9 -17)"/><path fill="#02CBB1" d="M86.755 88.926l3.46-15.26-39.363 10.387-3.459 15.261z"/><path fill="#0270D7" d="M90.735 53.81l7.137-28.53-77.08 20.354-7.137 28.533z"/><path fill-opacity=".32" fill="#00BFFB" d="M77.35 14.62l6.442-1.712-11.3-12.65-6.443 1.713z"/><path fill="#474C59" d="M46.63 33.718L28.384 61.923l43.659 19.72 18.244-28.206z"/><path fill="#00BFFB" d="M29.746 92.298l3.792-3.59-14.732-4-3.793 3.59z"/><path fill="#2C3039" fill-rule="nonzero" d="M17.685 36.372l10.7 25.55 18.339-28.026L35.015 8.122z"/><path fill="url(#b)" fill-rule="nonzero" d="M49.401 21.254L1.988 27.601l23.588 15.653 47.412-6.346z" transform="rotate(32 52.468 75.451)"/></g></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg width="115" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><filter x="-90%" y="-90%" width="280%" height="280%" filterUnits="objectBoundingBox" id="a"><feGaussianBlur stdDeviation="12" in="SourceGraphic"/></filter><path id="b" d="M0 .856h63.533v59.675H0z"/><linearGradient x1="56.146%" y1="10.87%" x2="25.695%" y2="72.649%" id="c"><stop stop-color="#2C3039" offset="0%"/><stop stop-color="#8A94A7" offset="48.131%"/><stop stop-color="#2C3039" offset="100%"/></linearGradient><path id="e" d="M.062.344h40.511v44.1H.062z"/><linearGradient x1="49.892%" y1=".428%" x2="24.856%" y2="100%" id="f"><stop stop-color="#3B404C" offset="0%"/><stop stop-color="#333843" offset="100%"/></linearGradient></defs><g fill="none" fill-rule="evenodd"><path fill="#0270D7" d="M23.302 63.083L.112 44.995l73.273-31.416 23.19 18.091z"/><path fill="#00BFFB" d="M35.902 83.663l1.17-6.562-16.24 4.895-1.17 6.563z"/><path fill-opacity=".32" fill="#00BFFB" d="M41.972 92.478l-2.962 3.376 11.56 2.714 2.961-3.376z"/><path fill-opacity=".48" fill="#1D2026" filter="url(#a)" d="M36.056 33.707h40v40h-40z" transform="rotate(16 55.276 48.942)"/><g transform="rotate(16 -28.89 116.929)"><mask id="d" fill="#fff"><use xlink:href="#b"/></mask><path d="M32.628 40.394c.06-.485.116-.972.204-1.45.055-.295.135-.583.2-.874.096-.427.181-.856.296-1.276.084-.303.192-.596.286-.894.128-.401.246-.808.393-1.202.05-.141.113-.275.169-.414.195-.5.4-.997.624-1.483.103-.22.214-.434.322-.65a28.007 28.007 0 0 1 2.005-3.412c.07-.1.15-.192.219-.29.448-.622.922-1.222 1.418-1.801.172-.198.347-.391.523-.584.464-.508.946-.993 1.444-1.46.175-.162.343-.334.522-.493.13-.113.263-.22.392-.332a25.56 25.56 0 0 1 1.147-.91c.146-.11.291-.22.44-.325.356-.254.723-.49 1.093-.723.212-.134.42-.276.637-.402.428-.252.868-.48 1.311-.702.157-.08.307-.169.465-.244a22.326 22.326 0 0 1 1.727-.721c.162-.06.326-.111.49-.168.45-.155.905-.293 1.366-.417.178-.047.354-.097.532-.14a20.012 20.012 0 0 1 1.862-.362 18.96 18.96 0 0 1 1.675-.155c.175-.01.347-.008.52-.013.382-.008.76-.008 1.134.005a17.935 17.935 0 0 1 1.654.144c.174.022.347.042.518.07.44.071.874.161 1.301.266.075.019.154.03.229.05.508.132 1.003.29 1.49.467.143.05.28.11.42.166.347.136.691.282 1.025.44.288.135.575.268.852.419L31.118 2.89a18.16 18.16 0 0 0-.619-.319c-.075-.038-.156-.065-.233-.1-.337-.16-.68-.305-1.028-.442-.14-.055-.276-.115-.417-.165-.49-.176-.985-.335-1.493-.466l-.03-.01c-.063-.016-.13-.024-.192-.04-.43-.105-.865-.195-1.307-.267-.17-.027-.344-.047-.518-.07a17.934 17.934 0 0 0-1.654-.144 17.699 17.699 0 0 0-1.133-.004c-.174.004-.346.003-.52.012-.554.03-1.112.076-1.678.155a20.56 20.56 0 0 0-1.86.364c-.178.04-.355.09-.532.139-.46.122-.917.262-1.368.417-.162.055-.326.106-.488.168-.566.209-1.126.436-1.674.694l-.054.027c-.155.074-.304.162-.458.24a22.56 22.56 0 0 0-1.318.705c-.215.127-.421.268-.633.401-.276.174-.558.338-.826.524-.093.064-.181.135-.272.2-.148.106-.293.217-.438.325-.39.291-.77.594-1.145.91-.132.112-.264.219-.394.333-.065.058-.136.11-.2.169-.113.102-.21.22-.32.324-.5.465-.982.952-1.445 1.46-.097.106-.208.197-.303.304-.079.09-.143.19-.22.281a26.06 26.06 0 0 0-1.42 1.8c-.057.08-.125.152-.182.233-.013.018-.023.038-.036.057a26.58 26.58 0 0 0-1.459 2.366c-.047.087-.108.167-.156.253-.138.26-.26.528-.391.792-.107.217-.218.43-.32.65-.225.485-.43.98-.625 1.481-.054.14-.118.276-.17.417l-.029.07c-.135.368-.242.749-.361 1.126-.094.3-.204.594-.287.899-.119.425-.204.859-.3 1.29-.065.287-.143.57-.197.86-.09.484-.145.974-.205 1.463-.03.247-.08.488-.102.734-.07.74-.107 1.485-.108 2.233-.014 7.778 3.736 14.079 9.395 17.158L41.813 60.53c-5.66-3.08-9.41-9.38-9.395-17.156.001-.75.037-1.495.107-2.235.024-.25.072-.496.103-.746" fill="url(#c)" mask="url(#d)"/></g><g transform="rotate(16 -109.581 222.973)"><mask id="g" fill="#fff"><use xlink:href="#e"/></mask><path d="M20.358.531C31.545-1.04 40.595 7.473 40.574 19.548c-.023 12.075-9.11 23.137-20.297 24.71C9.09 45.83.04 37.316.062 25.24.084 13.166 9.172 2.103 20.358.531" fill="url(#f)" mask="url(#g)"/></g><path fill="#02CBB1" d="M114.324 47.62l-10.196-8.59-7.761 33.025 10.197 8.59z"/></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

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