Giter VIP home page Giter VIP logo

chatroom-webrtc's Introduction

Mediasoup-demo v3 - Docker, Kubernetes and Turn Server

This is a fork of the demo application of mediasoup v3.

This fork is containerized and pre-configured to work with a turn server like coturn.

You can run the demo in Docker with coturn or in Kubernetes with stunner as turn server.

Table of Content

Pre-requisite

  • A Linux server with a public ip address (or an EIP on AWS)
  • Docker and Docker Compose
  • A turn server running in a container with port 3478 forwarded from the public ip (this is configured in docker)
  • A mediasoup server running in a container with port 4443 forwarded from the public ip (this is configured in docker)

Diagram

Alt text

Modification

Below are the modification that I've done starting from mediasoup-demo

  • Added a multistage Dockerfile

    • stage 0: run gulp dist to create the frontend app file (they will be served by nodejs from the backend)
    • stage 1: build the image for the mediasoup-server and copy the mediasoup-client file
  • added a start.sh file for the dockerimage, this is a simple script that will gather the ip "inside" of the container and use it for MEDIASOUP_ANNOUNCED_IP then start node /service/server.js

This is only needed for docker if you don't use net=host or for Kubernetes

  • added the following in server.js in the function "async function createExpressApp()" to serve the mediasoup-client file
147:	expressApp.use(express.static('public'))
services:
  mediasoup:
    image: mediasoup-demo-docker
    ports:
      - '443:443'
  coturn:
    image: coturn/coturn
    command: -n --log-file=stdout --lt-cred-mech --fingerprint --no-multicast-peers --no-cli --no-tlsv1 --no-tlsv1_1 --realm=my.realm.org --user user:pass -v
    ports:
      - "3478:3478"
      - "3478:3478/udp"

As you can see there only port 443 is "open" for Mediasoup and port 3478 for Coturn. This mean that all the webrtc media traffic is over udp 3478. This is a pre-requiste to be able to run this on Kubernetes without network=host and with a turn server like stunner.

  • Added a cert folder with self signed demo certificate in server/certs

you should replace the cert with your own

  • config.js file that work with Docker

How to change the tcp port (Web app and WSS).

This need to be changed in the frontend mediasoup-client and in the backend at the same time.

For the server, the port configured in server/config.js with the environement variable PROTOO_LISTEN_PORT.

In the client code (server/app/lib/urlFactory.js) there is a variable called protooPort that can only be changed before building the mediasoup-client files.

  • Change the port for the mediasoup-client (used for the WSS requests), replace 443 with the port you want to use (if you want to be able to run this demo as is you should keep port 443)
git clone https://github.com/damhau/mediasoup-demo-docker
vi server/Dockerfile
ENV MEDIASOUP_CLIENT_PROTOOPORT=443

โ— if you change this you have to rebuild the docker image

  • Change the port for the mediasoup-server, replace 443 with the port you want to use
git clone https://github.com/damhau/mediasoup-demo-docker
vi docker-compose.yml
services:
  mediasoup:
    image: mediasoup-demo-docker
    environment:
      PROTOO_LISTEN_PORT: 443
    ports:
      - '443:443'

Docker - How to build

  • Clone the repo
  • Run docker build in the server folder
git clone https://github.com/damhau/mediasoup-demo-docker
cd server
docker build . -t mediasoup-demo-docker

if the start.sh script fail to detect the container ip you can change the Dockerfile and replace CMD ["sh", "/service/start.sh"] with CMD ["node", "/service/server.js"] and set the variable MEDIASOUP_ANNOUNCED_IP manually

Docker - How to run

  • Edit server/docker-compose.yml and udpate the docker image for mediasoup with your own (otherwise it will use my turn server and it will not work)
git clone https://github.com/damhau/mediasoup-demo-docker
cd server
vi docker-compose.yml
replace image: mediasoup-demo-docker with your image
  • Run docker-compose up in the server folder
git clone https://github.com/damhau/mediasoup-demo-docker
cd server
docker-compose up
Recreating server_mediasoup_1 ... done
Starting server_coturn_1      ... done
Attaching to server_mediasoup_1, server_coturn_1
coturn_1     | 0: (1): INFO: System cpu num is 32
coturn_1     | 0: (1): INFO: System cpu num is 32
coturn_1     | 0: (1): INFO: System enable num is 12
coturn_1     | 0: (1): WARNING: Cannot find config file: turnserver.conf. Default and command-line settings will be used.
mediasoup_1  | running mediasoup-demo server.js with ip 172.19.0.3
coturn_1     | 0: (1): INFO: Coturn Version Coturn-4.6.2 'Gorst'
coturn_1     | 0: (1): INFO: Max number of open files/sockets allowed for this process: 1048576
coturn_1     | 0: (1): INFO: Due to the open files/sockets limitation, max supported number of TURN Sessions possible is: 524000 (approximately)
coturn_1     | 0: (1): INFO: 
coturn_1     | 
coturn_1     | ==== Show him the instruments, Practical Frost: ====
coturn_1     | 
coturn_1     | 0: (1): INFO: OpenSSL compile-time version: OpenSSL 3.0.9 30 May 2023 (0x30000090)
coturn_1     | 0: (1): INFO: TLS 1.3 supported
coturn_1     | 0: (1): INFO: DTLS 1.2 supported
coturn_1     | 0: (1): INFO: TURN/STUN ALPN supported
coturn_1     | 0: (1): INFO: Third-party authorization (oAuth) supported
coturn_1     | 0: (1): INFO: GCM (AEAD) supported
coturn_1     | 0: (1): INFO: SQLite supported, default database location is /var/lib/coturn/turndb
coturn_1     | 0: (1): INFO: Redis supported
coturn_1     | 0: (1): INFO: PostgreSQL supported
coturn_1     | 0: (1): INFO: MySQL supported
coturn_1     | 0: (1): INFO: MongoDB supported
coturn_1     | 0: (1): INFO: Default Net Engine version: 3 (UDP thread per CPU core)
coturn_1     | 0: (1): INFO: Domain name: 
coturn_1     | 0: (1): INFO: Default realm: my.realm.org
coturn_1     | 0: (1): ERROR: CONFIG: Unknown argument: 
mediasoup_1  | process.env.DEBUG: *INFO* *WARN* *ERROR*
mediasoup_1  | config.js:
mediasoup_1  | {
mediasoup_1  |   "https": {
mediasoup_1  |     "listenIp": "0.0.0.0",
mediasoup_1  |     "listenPort": 4443,
mediasoup_1  |     "tls": {
mediasoup_1  |       "cert": "/service/certs/fullchain.pem",
mediasoup_1  |       "key": "/service/certs/privkey.pem"
mediasoup_1  |     }
mediasoup_1  |   },
mediasoup_1  |   "mediasoup": {
mediasoup_1  |     "numWorkers": 12,
mediasoup_1  |     "workerSettings": {
mediasoup_1  |       "logLevel": "warn",
mediasoup_1  |       "logTags": [
mediasoup_1  |         "info",
mediasoup_1  |         "ice",
mediasoup_1  |         "dtls",
mediasoup_1  |         "rtp",
mediasoup_1  |         "srtp",
mediasoup_1  |         "rtcp",
mediasoup_1  |         "rtx",
mediasoup_1  |         "bwe",
mediasoup_1  |         "score",
mediasoup_1  |         "simulcast",
mediasoup_1  |         "svc",
mediasoup_1  |         "sctp"
mediasoup_1  |       ],
mediasoup_1  |       "rtcMinPort": 40000,
mediasoup_1  |       "rtcMaxPort": 40099
mediasoup_1  |     },
mediasoup_1  |     "routerOptions": {
mediasoup_1  |       "mediaCodecs": [
mediasoup_1  |         {
mediasoup_1  |           "kind": "audio",
mediasoup_1  |           "mimeType": "audio/opus",
mediasoup_1  |           "clockRate": 48000,
mediasoup_1  |           "channels": 2
mediasoup_1  |         },
mediasoup_1  |         {
mediasoup_1  |           "kind": "video",
mediasoup_1  |           "mimeType": "video/VP8",
mediasoup_1  |           "clockRate": 90000,
mediasoup_1  |           "parameters": {
mediasoup_1  |             "x-google-start-bitrate": 1000
mediasoup_1  |           }
mediasoup_1  |         },
mediasoup_1  |         {
mediasoup_1  |           "kind": "video",
mediasoup_1  |           "mimeType": "video/VP9",
mediasoup_1  |           "clockRate": 90000,
mediasoup_1  |           "parameters": {
mediasoup_1  |             "profile-id": 2,
mediasoup_1  |             "x-google-start-bitrate": 1000
mediasoup_1  |           }
mediasoup_1  |         },
mediasoup_1  |         {
mediasoup_1  |           "kind": "video",
mediasoup_1  |           "mimeType": "video/h264",
mediasoup_1  |           "clockRate": 90000,
mediasoup_1  |           "parameters": {
mediasoup_1  |             "packetization-mode": 1,
mediasoup_1  |             "profile-level-id": "4d0032",
mediasoup_1  |             "level-asymmetry-allowed": 1,
mediasoup_1  |             "x-google-start-bitrate": 1000
mediasoup_1  |           }
mediasoup_1  |         },
mediasoup_1  |         {
mediasoup_1  |           "kind": "video",
mediasoup_1  |           "mimeType": "video/h264",
mediasoup_1  |           "clockRate": 90000,
mediasoup_1  |           "parameters": {
mediasoup_1  |             "packetization-mode": 1,
mediasoup_1  |             "profile-level-id": "42e01f",
mediasoup_1  |             "level-asymmetry-allowed": 1,
mediasoup_1  |             "x-google-start-bitrate": 1000
mediasoup_1  |           }
mediasoup_1  |         }
mediasoup_1  |       ]
mediasoup_1  |     },
mediasoup_1  |     "webRtcServerOptions": {
mediasoup_1  |       "listenInfos": [
mediasoup_1  |         {
mediasoup_1  |           "protocol": "udp",
mediasoup_1  |           "ip": "0.0.0.0",
mediasoup_1  |           "announcedIp": "172.19.0.3",
mediasoup_1  |           "port": 44444
mediasoup_1  |         },
mediasoup_1  |         {
mediasoup_1  |           "protocol": "tcp",
mediasoup_1  |           "ip": "0.0.0.0",
mediasoup_1  |           "announcedIp": "172.19.0.3",
mediasoup_1  |           "port": 44444
mediasoup_1  |         }
mediasoup_1  |       ]
mediasoup_1  |     },
mediasoup_1  |     "webRtcTransportOptions": {
mediasoup_1  |       "listenIps": [
mediasoup_1  |         {
mediasoup_1  |           "ip": "0.0.0.0",
mediasoup_1  |           "announcedIp": "172.19.0.3"
mediasoup_1  |         }
mediasoup_1  |       ],
mediasoup_1  |       "initialAvailableOutgoingBitrate": 1000000,
mediasoup_1  |       "minimumAvailableOutgoingBitrate": 600000,
mediasoup_1  |       "maxSctpMessageSize": 262144,
mediasoup_1  |       "maxIncomingBitrate": 1500000
mediasoup_1  |     },
mediasoup_1  |     "plainTransportOptions": {
mediasoup_1  |       "listenIp": {
mediasoup_1  |         "ip": "0.0.0.0",
mediasoup_1  |         "announcedIp": "172.19.0.3"
mediasoup_1  |       },
mediasoup_1  |       "maxSctpMessageSize": 262144
mediasoup_1  |     }
mediasoup_1  |   }
mediasoup_1  | }

Docker - How to test

Check that announcedIp is the ip "inside" of the mediasoup container, it should not be the public ip as all the media traffic will be relayed via the public ip of coturn.

  • Open a brower to https://you_public_ip:4443?enableIceServer=yes&iceServerHost=100.100.100.100&iceServerPort=3478&iceServerProto=udp&iceServerUser=user-1&iceServerPass=pass-1, you should get the mediasoup client demo app

    • replace 100.100.100.100 with the public ip of your turn server
    • replace 3478 with the port of your turn server
    • replace udp with the protocal of your turn server
    • replace user-1 and pass-1 with the user and pass of your turn server
  • Open chrome://webrtc-internals/ in chrome the two webrtc stream should be like this

- ICE connection state: new => completed
Connection state: new => connected
Signaling state: new => stable
ICE Candidate pair: 172.19.0.2:55286 <=> 172.19.0.3:44444

The ip address should be the private ip address of the mediasoup and coturn container

  • The Ice candidate grid should look like this

image

  • In the Mediasoup client demo app click on the link Invitation Link and open this link from another computer or you mobile phone

  • Both device should be in the Room

Kubernetes - How to run

Prerequiste

  • A Kubernetes Cluster with loadbalancer support (AKS, GKE or On premise with MetaLB). I will use Azure AKS in the example but it should work with other.
  • Kubectl
  • Helm
  • Nginx Ingress
  • Cert manager
  • Stunner

Container image

  • Build the mediasoup demo image like document above.

Ingress

Install an ingress controller into your cluster. We used the official nginx ingress, but this is not required.

NAMESPACE=ingress-nginx

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update

helm install ingress-nginx ingress-nginx/ingress-nginx \
  --create-namespace \
  --namespace $NAMESPACE \
  --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz

โ— The example above is for ngix ingress on AKS if you deploy it on another K8S please remove/change the --set controller.service.annotations

Wait until Kubernetes assigns an external IP to the Ingress.

until [ -n "$(kubectl -n ingress-nginx get service ingress-nginx-controller -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" ]; do sleep 1; done

Cert manager

We use the official cert-manager to automate TLS certificate management.

First, install cert-manager's CRDs.

kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.8.0/cert-manager.crds.yaml

Then add the Helm repository, which contains the cert-manager Helm chart, and install the charts:

helm repo add cert-manager https://charts.jetstack.io
helm repo update
helm install my-cert-manager cert-manager/cert-manager \
    --create-namespace \
    --namespace cert-manager \
    --version v1.8.0

STUNner

Install the STUNner gateway operator and STUNner via Helm:

helm repo add stunner https://l7mp.io/stunner
helm repo update
helm install stunner-gateway-operator stunner/stunner-gateway-operator --create-namespace --namespace=stunner-system
helm install stunner stunner/stunner --create-namespace --namespace=stunner

Configure STUNner to act as a STUN/TURN server to clients, and route all received media to the Mediaserver server pods. Deploy the following resrouce with kubectl apply

echo 'apiVersion: gateway.networking.k8s.io/v1alpha2
kind: GatewayClass
metadata:
  name: stunner-gatewayclass
spec:
  controllerName: "stunner.l7mp.io/gateway-operator"
  parametersRef:
    group: "stunner.l7mp.io"
    kind: GatewayConfig
    name: stunner-gatewayconfig
    namespace: stunner
  description: "STUNner is a WebRTC ingress gateway for Kubernetes"' | kubectl apply -f -
echo 'apiVersion: stunner.l7mp.io/v1alpha1
kind: GatewayConfig
metadata:
  name: stunner-gatewayconfig
  namespace: stunner
spec:
  realm: stunner.l7mp.io
  authType: plaintext
  userName: "user-1"
  password: "pass-1"' | kubectl apply -f -
echo "apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
  name: udp-gateway
  namespace: stunner
spec:
  gatewayClassName: stunner-gatewayclass
  listeners:
    - name: udp-listener
      port: 3478
      protocol: UDP" | kubectl apply -n stunner -f -
echo "apiVersion: gateway.networking.k8s.io/v1alpha2
kind: UDPRoute
metadata:
  name: livekit-media-plane
  namespace: stunner
spec:
  parentRefs:
    - name: udp-gateway
  rules:
    - backendRefs:
      - group: ""
        kind: Service
        name: mediasoup-server
        namespace: mediasoup" | kubectl apply -n stunner -f -

Once the Gateway resource is installed into Kubernetes, STUNner will create a Kubernetes LoadBalancer for the Gateway to expose the TURN server on UDP:3478 to clients. It can take up to a minute for Kubernetes to allocate a public external IP for the service.

Wait until Kubernetes assigns an external IP and store the external IP assigned by Kubernetes to STUNner in an environment variable for later use.

until [ -n "$(kubectl get svc udp-gateway -n stunner -o jsonpath='{.status.loadBalancer.ingress[0].ip}')" ]; do sleep 1; done
export STUNNERIP=$(kubectl get service udp-gateway -n stunner -o jsonpath='{.status.loadBalancer.ingress[0].ip}')

Mediasoup

  • build the container image as documented above and

  • create the mediasoup namespace

kubectl create namespace mediasoup
  • deploy mediasoup on Kubernetes
echo "kind: Deployment
metadata:
  labels:
    app.kubernetes.io/name: mediasoup-server
  name: mediasoup-server
  namespace: mediasoup
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app.kubernetes.io/name: mediasoup-server
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app.kubernetes.io/name: mediasoup-server
    spec:
      containers:
      - env:
        - name: PROTOO_LISTEN_PORT
          value: "443"
        image: mediasoup-demo-docker
        imagePullPolicy: IfNotPresent
        name: mediasoup-server
        ports:
        - containerPort: 80
          name: http
          protocol: TCP
        - containerPort: 443
          name: https
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30" | kubectl apply -n mediasoup -f -
  • create mediasoup service on Kubernetes
echo "apiVersion: v1
kind: Service
metadata:
  name: mediasoup-server
  namespace: mediasoup
spec:
  ports:
  - name: https-443
    port: 443
    protocol: TCP
    targetPort: 443
  selector:
    app.kubernetes.io/name: mediasoup-server
  type: ClusterIP" | kubectl apply -n mediasoup -f -
  • Create a dns entry for mediasoup that point to the public ip address of nginx ingress
kubectl -n ingress-nginx get svc 
NAME                                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)                      AGE
ingress-nginx-controller-admission   ClusterIP      10.0.23.140    <none>        443/TCP                      6d21h
ingress-nginx-controller             LoadBalancer   10.0.23.194   100.100.100.101   80:30947/TCP,443:31839/TCP   6d21h

use the external ip to create you dns entry, eg: mediasoup.yourdomain.com -> A record to 100.100.100.101

  • Create a clusterissuer to automate the certificate for you ingress
echo "apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  generation: 1
  name: letsencrypt-prod
spec:
  acme:
    email: [email protected]
    privateKeySecretRef:
      name: letsencrypt-secret-prod
    server: https://acme-v02.api.letsencrypt.org/directory
    solvers:
    - http01:
        ingress:
          class: nginx" | kubectl apply -f -
  • Create an ingress for mediasoup
echo "apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/backend-protocol: HTTPS
    nginx.ingress.kubernetes.io/upstream-hash-by: "$arg_roomId"
  name: mediasoup-server
  namespace: mediasoup
spec:
  rules:
  - host: mediasoup.yourdomain.com
    http:
      paths:
      - backend:
          service:
            name: mediasoup-server
            port:
              number: 443
        path: /
        pathType: Prefix
  tls:
  - hosts:
    - mediasoup.yourdomain.com
    secretName: mediasoup-demo-tls" | kubectl apply -n mediasoup -f -

The annotation nginx.ingress.kubernetes.io/upstream-hash-by: "$arg_roomId" allow to scale the media server by increasing the number of replica of the deploymynet, the configure nginx to send the client in the same room to the same media server pod.

Kubernetes - How to test

  • Check that your mediaserver pod is started
kubectl -n mediasoup get pod -o wide
NAME                               READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
mediasoup-server-7bbcd6879-kgsv6   1/1     Running   0          80m   10.80.63.71   aks-nodepool   <none>           <none>

Status should be running

  • Review the log of the mediasoup server
kubectl -n mediasoup logs -l app.kubernetes.io/name --tail=10000
running mediasoup-demo server.js with ip 10.80.63.71
process.env.DEBUG: *INFO* *WARN* *ERROR*
config.js:
{
  "https": {
    "listenIp": "0.0.0.0",
    "listenPort": "443",
    "tls": {
      "cert": "/service/certs/fullchain.pem",
      "key": "/service/certs/privkey.pem"
    }
  },

the ip after "running mediasoup-demo server.js with ip" should be the ip of the mediaserver pod if this is not the case the start.sh script of the docker image was not able to "detec" the ip address of the pod

  • if the pod ip is not detected when mediasoup server start you can try the following:

    • Edit /server/start.sh and comment line 3 and 5 and rebuild the docker image

    • Edit you mediasoup kubernetes deployment replace it with the follwoing, the important bit is valueFrom which will set the env var MEDIASOUP_ANNOUNCED_IP with the ip address of the pod

      echo "kind: Deployment
      metadata:
        labels:
          app.kubernetes.io/name: mediasoup-server
        name: mediasoup-server
        namespace: mediasoup
      spec:
        progressDeadlineSeconds: 600
        replicas: 1
        revisionHistoryLimit: 10
        selector:
          matchLabels:
            app.kubernetes.io/name: mediasoup-server
        strategy:
          rollingUpdate:
            maxSurge: 25%
            maxUnavailable: 25%
          type: RollingUpdate
        template:
          metadata:
            labels:
              app.kubernetes.io/name: mediasoup-server
          spec:
            containers:
            - env:
              - name: PROTOO_LISTEN_PORT
                value: "443"
              - name: MEDIASOUP_ANNOUNCED_IP
                valueFrom:
                  fieldRef:
                    apiVersion: v1
                    fieldPath: status.podIP
              image: mediasoup-demo-docker
              imagePullPolicy: IfNotPresent
              name: mediasoup-server
              ports:
              - containerPort: 80
                name: http
                protocol: TCP
              - containerPort: 443
                name: https
                protocol: TCP
              resources: {}
              terminationMessagePath: /dev/termination-log
              terminationMessagePolicy: File
            dnsPolicy: ClusterFirst
            restartPolicy: Always
            schedulerName: default-scheduler
            securityContext: {}
            terminationGracePeriodSeconds: 30" | kubectl apply -n mediasoup -f -
  • check you mediasoup service

kubectl -n mediasoup get service mediasoup-server 
NAME               TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
mediasoup-server   ClusterIP   10.0.47.100   <none>        443/TCP   5d16h
  • check your mediasoup ingress
kubectl -n mediasoup get ingress mediasoup-server 
NAME               CLASS    HOSTS                            ADDRESS       PORTS     AGE
mediasoup-server   <none>   mediasoup.youdomain.com          100.100.100.101   80, 443   5d16h
  • check if certmanager generated a Letencrypt cert for your mediasoup ingress
kubectl -n mediasoup get certificate
NAME                 READY   SECRET               AGE
mediasoup-demo-tls   True    mediasoup-demo-tls   77m
  • Open a brower to https://you_public_ip:4443?enableIceServer=yes&iceServerHost=100.100.100.100&iceServerPort=3478&iceServerProto=udp&iceServerUser=user-1&iceServerPass=pass-1, you should get the mediasoup client demo app

    • replace 100.100.100.100 with the public ip of your turn server
    • replace 3478 with the port of your turn server
    • replace udp with the protocal of your turn server
    • replace user-1 and pass-1 with the user and pass of your turn server
  • Open chrome://webrtc-internals/ in chrome the two webrtc stream should be like this

- ICE connection state: new => completed
Connection state: new => connected
Signaling state: new => stable
ICE Candidate pair: 10.19.0.2:55286 <=> 10.19.0.3:44444

The ip address should be the private ip address of the mediasoup pod and coturn pod

  • The Ice candidate grid should look like this

image

  • In the Mediasoup client demo app click on the link Invitation Link and open this link from another computer or you mobile phone

  • Both device should be in the Room

Improvement

  • Add an config example with a turn server with TLS on port 443
  • Add a simple api on the mediasoup server to provide the turn config for the client

chatroom-webrtc's People

Contributors

damhau avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.