WebRTC

General

General

NAT traversal

  • Info
  • NAT types
    • Network address translation (wp)



    • description

      example
      cone
      use the same port numbers for internal and external IP tuples
      full cone
      allows inbound connections from any source IP address and any source port, as long as the destination tuple exists in a previously created rule.
          {NAT internal side}  |    {NAT external side}  |  {Remote machine}
                               |                         |
      1. (INT_ADDR, INT_PORT) => [ (EXT_ADDR, INT_PORT) -> (REM_ADDR, REM_PORT) ]
      2. (INT_ADDR, INT_PORT) <= [ (EXT_ADDR, INT_PORT) <- (   *    ,    *    ) ]

      port forwarding


      (address-) restricted cone





      port-restricted cone



      symmetric always use different numbers for each side of the NAT




    • STUN/TURN (NAT Types and NAT Traversal)

      • symmetric port-restricted cone
        address-restricted cone
        full cone
        symmetric
        TURN
        TURN STUN
        STUN
        port-restricted cone TURN STUN STUN STUN
        address-restricted cone STUN STUN STUN STUN
        full cone STUN STUN STUN STUN
    • Tools to determine the type of NAT
  • Eines i protocols / Tools and protocols


    • RFC
      port
      programari / software
      description



      UDP
      TCP
      (D)TLS


      ICE
      Interactive Connectivity Establishment
      5245




      "ICE protocol provides a structured mechanism to determine the optimal communication path between two peers." (wp)
      "Coordinates the optimal method of establishing connectivity between two peers using STUN and TURN" (en)
      STUN Session Traversal Utilities for NAT 3489 (CLASSIC-STUN)
      5389 (STUN)
      3478
      5349 "The client, typically operating inside a private network, sends a binding request to a STUN server on the public Internet. The STUN server responds with a success response that contains the IP address and port number of the client, as observed from the server's perspective." (wp)
      TURN Traversal Using Relay NAT 5766
      6156
      7065



      "TURN places a third-party server to relay messages between two clients when direct media traffic between peers is not allowed by a firewall." (wp)
      "Allows an application or client to obtain IP addresses and ports from an external relay server in order to communicate with a peer" (en)
    • ICE
      • ICE (wp)
      • RFC 5245
      • Candidate transport addresses



        • host address
          host
          directly attached to a network interface
          server reflexive address
          srflx public side of a NAT (STUN)
          relay
          relay
          allocated from a TURN server
        • server reflexive (srflx)
        • peer reflexive (prflx)
        • relay
      • 2.2 Connectivity checks
        1. Sort the candidate pairs in priority order.
        2. Send checks on each candidate pair in priority order.
        3. Acknowledge checks received from the other agent.
      • ICE selects a valid pair of candidates
      • 4. Sending the Initial Offer
        • agent must:
          1. gather candidates
          2. prioritize them
          3. eliminate redundant candidates
          4. choose default candidates
          5. formulate and send the SDP offer
    • STUN
      • STUN (wp)
      • List of 267 public STUN servers from EmerCoin project. Tested 2017-08-08
      • STUN DTLS
      • works with NAT types (Limitations):
        • full cone NAT
        • restricted cone NAT
        • port restricted cone NAT
      • does not work with NAT types:
        • symmetric (bi-directional) NAT
      • Wireshark
        • filter: classicstun (CLASSIC-STUN) (RFC 3489)
        • stunc ... -b
          • -> Binding Request
          • <- Binding Response
      • from bash
        • How can I get my external IP address in a shell script?
        • clients
          • WebRTC samples Trickle ICE
            • If you test a STUN server, it works if you can gather a candidate with type "srflx".
            • If you test a TURN server, it works if you can gather a candidate with type "relay".
          • stunc (Sofia-SIP)
            • stunc stun.l.google.com:19302 -b
            • stunc stun.voip.eutelia.it:3478 -b
            • NAT type
              • stunc stun.voip.eutelia.it:3478 -n
            • TLS
              • on server, TCP/3478 must be open
          • Stuntman
            • STUN server and client
            • Compilation
              • Dependencies
                • Mageia
                  • urpmi install lib64boost-devel
              • Steps
                • cd stunserver
                • make
            • Installation
              • Debian /Ubuntu
                • apt-get install stuntman-client
            • Ús / Usage
              • ./stunclient stun.e-fon.ch 3478
    • TURN
  • Servidors / Servers
    • rfc5766-turn-server
    • coTURN
      • Instal·lació / Installation

      • AWS coturn config test from client

        security group /etc/coturn/turnserver.conf protocol stunc (sofia) turnutils (coturn) TrickleICE
        non secure TCP, UDP 3478
        STUN stunc myturnserver -b turnutils_stunclient myturnserver
        • STUN or TURN URI: stun:myturnserver
        user=guest:somepassword TURN
        turnutils_uclient -v -u guest -w somepassword -p 3478 myturnserver
        • STUN or TURN URI: turn:myturnserver
        • TURN username: guest
        • TURN password: somepassword
        secure TCP, UDP 5349 #tls-listening-port=5349
        cert=/etc/letsencrypt/live/toto.org/fullchain.pem
        pkey=/etc/letsencrypt/live/toto.org/privkey.pem
        STUN stunc myturnserver:5349 -b turnutils_stunclient -p 5349 myturnserver
        • STUN or TURN URI: stun:myturnserver:5349
        #tls-listening-port=5349
        cert=/etc/letsencrypt/live/toto.org/fullchain.pem
        pkey=/etc/letsencrypt/live/toto.org/privkey.pem
        user=guest:somepassword
        TURN
        turnutils_uclient -v -u guest -w somepassword -p 5349 myturnserver
        • STUN or TURN URI: turn:myturnserver:5349
        • TURN username: guest
        • TURN password: somepassword
      • Config
        • Transport between TURN client and TURN server (RFC 8656):

          client
          turnserver.conf



          turnutils_uclient test.webrtc.org Firefox



          no-udp
          no-dtls
          no-tcp
          no-tls

          about:webrtc
          UDP turn:myturnserver:3478
          listening-port=3478 -



          [-p 3478] Network: UDP enabled
          DTLS over UDP turns:myturnserver:5349 tls-listening-port=5349
          -

          -p 5349 Network: UDP enabled
          TCP turn:myturnserver:3478 listening-port=3478

          -

          [-p 3478] -t Network: TCP enabled
          TLS over TCP turns:myturnserver:5349 tls-listening-port=5349


          -
          -p 5349 -t Network: TCP enabled
          Note: on server, use ss -tulpn | grep turn to check open ports.
          Gathered candidates (relayed transport address) can be UDP, even if transport between TURN client and TURN server is established over TCP.
        • Transport between TURN server and peer (candidate of type relay) (RFC 6062): (is transport=tcp also needed to specify tcp transport between TURN client and TURN server?)

          client turnserver.conf
          turnutils_uclient Firefox


          no-udp-relay
          (also disables udp, tcp communication between client/server,
          even if, according to ss, ports are available)
          no-tcp-relay
          (not working?)

          about:webrtc





          local candidate
          UDP (RFC 8656) turn:myturnserver:...[?transport=udp] -

          x.x.x.x:p/udp (relay-udp)
          DTLS over UDP turns:myturnserver:...[?transport=udp]


          x.x.x.x:p/udp (relay-tls)
          TCP (RFC 6062) turn:myturnserver:...?transport=tcp
          - -T (implies -t) x.x.x.x:p/udp (relay-tcp)
          TLS over TCP (RFC 6062) turns:myturnserver:...?transport=tcp


          x.x.x.x:p/udp (relay-tls)
        • activate TLS/DTLS
          • /etc/coturn/turnserver.conf
            • #tls-listening-port=5349
              cert=/etc/letsencrypt/live/toto.org/fullchain.pem
              pkey=/etc/letsencrypt/live/toto.org/privkey.pem
          • check from server
            • tail -n 200 /var/log/coturn/turnserver.log
            • ss -tulnp
          • check from remote client
            • stunc turn.watchity.net:5349 -b
            • turnutils_stunclient -p 5349 myturnserver
            • TrickleICE
              • STUN or TURN URI: stun:myturnserver:5349
              • Gather candidates:
                • srflx udp <my_external_ip_address>
        • non default ports (e.g. 443 for TLS)
          • coturn.service
            • [Service]
              AmbientCapabilities=CAP_NET_BIND_SERVICE
              ...
          • After update of coturn can not connect to 443 #421
          • without AmbientCapabilities, you will see, in /var/log/coturn/turnserver.log
            • ERROR: Fatal final failure: cannot bind DTLS/UDP listener socket to addr 127.0.0.1:443
        • add a user/password
          • option 1 (these credentials will not work with -u and -w options in turnutils_uclient):
            • /etc/coturn/turnserver.conf
              • user=guest:somepassword
          • option 2:
            • sudo turnadmin -a -r mycompany.org -u convidat -p contrasenya
          • check from remote client
            • turnutils_uclient -v -p 5349 -u convidat -w contrasenya myturnserver
            • TrickleICE
              • STUN or TURN URI: turns:myturnserver:5349
              • TURN username: guest
              • TURN password: somepassword
              • Gather candidates:
                • relay udp <turn_server_ip_address>
            • ...
        • dynamic credentials:
          • /etc/coturn/turnserver.conf
            • #lt-cred-mech
              use-auth-secret
              static-auth-secret=north
          • somewhere (e.g. a completely different server), generate temporary credentials, calculated using the shared secret:
            • usercombo -> "timestamp_when_password_will_expire:userid"
              turn_user -> usercombo
              turn_password -> base64(hmac_sha1(secret, usercombo))
          • put them when accessing the coturn server (e.g. from Janus echotest.js):
            • iceServers: [{urls: "turn:myturnserver:5349", username: turn_user, credential: turn_password},
          • test with TrickleICE:
            • STUN or TURN URI: turns:myturnserver:5349
            • TURN username: turn_user
            • TURN password: turn_password
            • Gather candidates (you can also filter by "relay" only):
              • relay udp <turn_server_ip_address>
        • logs
          • # sudo mkdir -p /var/log/coturn
            # sudo chown turnserver.turnserver /var/log/coturn
            log-file=/var/log/coturn/turnserver.log
            #syslog
            # Enable full ISO-8601 timestamp in all logs
            new-log-timestamp
      • AWS EC2
      • Servidor / Server
        • sudo systemctl enable coturn.service
        • sudo systemctl start coturn.service
        • sudo systemctl status coturn.service
      • Debug and logs
        • /var/log/coturn/turnserver.log
      • Utils and test (turnutils)
        • Instal·lació / Installation
          • from source
            • ...
          • CentOS
            • sudo dnf install coturn-utils
        • Ús / Usage
          • coturn/examples/scripts
          • turnadmin
          • man turnutils
          • turnutils_natdiscovery
          • turnutils_oauth
          • turnutils_peer
          • turnutils_stunclient
            • turnutils_stunclient myturnserver
          • turnutils_uclient
  • Debug
  • Demos
    • AppRTC (see logs, about:webrtc)

Programari /Software

Janus

  • Info
  • Dependències / Dependencies
    • Ubuntu
      • ...
    • CentOS
      • yum install epel-release git gcc
        yum install libmicrohttpd-devel jansson-devel openssl-devel libsrtp-devel glib-devel opus-devel libogg-devel libsoup-devel pkgconfig gengetopt libtool autoconf automake
        libnice-devel
        yum install doxygen graphviz
    • Mageia
    • libmicrohttpd
      • version >=0.9.59 is needed by Janus >=0.9.2
      • IMPORTANT: check that no system libmicrohttpd-devel is installed
      • from tar file:
        • wget https://ftp.gnu.org/gnu/libmicrohttpd/libmicrohttpd-0.9.71.tar.gz
        • tar xvf libmicrohttpd-0.9.71.tar.gz
        • cd libmicrohttpd-0.9.71
        • ./configure
        • make
        • sudo make install
    • Nice
      • WARNING: package provided by CentOS EPEL repositories (libnice-0.1.3-4.el7.x86_64) is too old and will give problems
      • Dependències / Dependencies
        • CentOS
          • sudo yum install glib2-devel
      • from tar file:
      • from git:
        • GitLab:
          • git clone https://gitlab.freedesktop.org/libnice/libnice.git
        • GitHub:
          • git clone https://github.com/libnice/libnice.git
        • cd libnice
      • compilació / compilation
        • ./configure
          make
          sudo make install
    • Sofia-SIP
      • Files
      • wget http://downloads.sourceforge.net/project/sofia-sip/sofia-sip/1.12.11/sofia-sip-1.12.11.tar.gz
        tar xvzf sofia-sip-1.12.11.tar.gz
        cd sofia-sip-1.12.11
        ./configure
        make
        sudo make install # will install it in /usr/local/lib

        sudo ldconfig
    • libsrtp
    • libusrsctp (needed when compiling Janus with --enable-data-channels)
      • Dependències / Dependencies
        • CentOS
          • sudo yum install libtool
      • git clone https://github.com/sctplab/usrsctp
        cd usrsctp
        ./bootstrap
        ./configure
        make
        sudo make install
        sudo ldconfig
    • libwebsockets (needed when compiling Janus with no --disable-websockets)
      • Dependències / Dependencies
        • CentOS
          • sudo yum install cmake openssl-devel
      • git clone https://libwebsockets.org/repo/libwebsockets
        cd libwebsockets
        # If you want the stable version of libwebsockets, uncomment the next line
        # git checkout v2.4-stable
        mkdir build
        cd build
        # See https://github.com/meetecho/janus-gateway/issues/732 re: LWS_MAX_SMP
        cmake -DLWS_MAX_SMP=1 -DCMAKE_INSTALL_PREFIX:PATH=/usr -DCMAKE_C_FLAGS="-fpic" ..
        make && sudo make install
    • libconfig (needed to have bash tool ls-config)
      • Dependències / Dependencies
        • CentOS
          • sudo yum install texinfo flex gcc-c++ bison
      • ...
        autoreconf
        ./configure
        make
        sudo make install
  • Janus compilation
    • # activate /usr/local/lib (nice, sofia-sip) for pkg-config
      export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig/

    • git clone https://github.com/meetecho/janus-gateway
      cd janus-gateway
      git checkout v0.9.1
      sh autogen.sh
      ./configure --disable-websockets --disable-data-channels --disable-rabbitmq --prefix=/usr --sysconfdir=/etc --localstatedir=/var
      make
      sudo make install 
    • websockets
      • install libwebsockets
      • ./configure --disable-data-channels --disable-rabbitmq --prefix=/usr --sysconfdir=/etc --localstatedir=/var
      • WebSockets API
    • install config files in /etc/janus
      • sudo make configs
    • Eclipse
      • New / C Project: GNU Autotools / Empty project
    • Forks
    • Problemes / Problems
      • when compiling with --enable-post-processing
        postprocessing/pp-h264.c: In function ‘janus_pp_h264_create’:
        postprocessing/pp-h264.c:99:29: error: assignment of member ‘video_codec’ in read-only object
          fctx->oformat->video_codec = codec->id;
                                     ^
        • Solució / Solution
          • check for different installed versions of ffmpeg (e.g. from ffmpeg compilation and gstreamer compilation)
            • compare:
              • ffmpeg -version
              • grep "define LIBAVCODEC_VERSION_" /usr/local/include/libavcodec/version.h
            • reinstall ffmpeg after gstreamer compilation
      • /usr/include/jansson.h:53:3: error: conflicting types for 'json_t'
        • Mageia: jansson-devel-2.4-4.1.mga5
        • Solució / Solution
          • /usr/include/jansson.h
            • typedef struct json_t {
                  json_type type;
                  size_t refcount;
              } json_t;
  • Service
    • Janus as a daemon/service
    • janus.service (/usr/lib/systemd/system/janus.service)
      • [Unit]
        Description=Janus WebRTC Gateway
        Wants=network.target
        After=syslog.target network.target remote-fs.target nss-lookup.target cloud-init.service

        [Service]
        Type=simple
        ExecStart=/usr/local/bin/janus
        #User=janus
        #Group=janus
        Restart=on-failure
        LimitNOFILE=65536

        [Install]
        WantedBy=multi-user.target
    • manage service
      • sudo systemctl status janus.service
      • sudo systemctl start janus.service
      • sudo systemctl stop janus.service
  • Configuració / Setup
    • /etc/janus/janus.jcfg
      • debug
        • general: {
                  log_to_file = "/var/log/janus/janus.log"             # Whether to use a log file or not
                  debug_level = 5                                      # Debug/logging level, valid values are 0-7
          ...
          }
    • /etc/janus/janus.cfg
      • debug
        • [general]
          log_to_file = /var/log/janus/janus.log
          debug_level = 7                         ; Debug/logging level, valid values are 0-7
          debug_timestamps = yes

          [nat]
          # maybe nice debug messages are only available when janus is called from command line, with:
          # export NICE_DEBUG=all
          # export G_MESSAGES_DEBUG=all
          # https://nice.freedesktop.org/libnice/libnice-Debug-messages.html
          nice_debug = true
      • nat (these values are for Janus itself. For final clients, modify the variable iceServers in javascript)
        • [nat]
          stun_server = stun.voip.eutelia.it
          stun_port = 3478
          turn_server =
          turn_port =
          turn_type =
          turn_user =
          turn_pwd =
          nice_debug = true
      • DTLS
        • ; Certificate and key to use for DTLS.
          [certificates]
          cert_pem = /usr/share/janus/certs/mycert.pem
          cert_key = /usr/share/janus/certs/mycert.key

          [media]
          ;dtls_mtu = 1200
      • admin
        • [general]
          admin_secret = mysecret
      • NACK
        • [media]
          max_nack_queue = 500
    • /etc/janus/janus.transport.http.cfg
      • CORS
        • [cors]
          allow_origin = https://<your_demos_server>:<your_demos_port> # e.g. https://192.168.1.100:8080
      • enable HTTPS on port 8089
        • [general]
          https = yes                    ; Whether to enable HTTPS (default=no)
          secure_port = 8089            ; Web server HTTPS port, if enabled

          ; Certificate and key to use for HTTPS.
          [certificates]
          cert_pem = /usr/share/janus/certs/mycert.pem
          cert_key = /usr/share/janus/certs/mycert.key
        • Let's Encrypt certificates
          • ; Certificate and key to use for HTTPS.
            [certificates]
            cert_pem = /etc/letsencrypt/live/www.example.org/fullchain.pem
            cert_key = /
            etc/letsencrypt/live/www.example.org/privkey.pem
          • Problemes / Problems
            • /var/log/janus/janus.log
              • Couldn't start secure webserver on port 8089...
              • Manually start janus as user janus, with high debug level
                • sudo -i
                • systemctl stop janus.service
                • su janus -s /bin/bash -c "/bin/janus -d 7"
              • Check that your user has access to certificate file:
                • su janus -s /bin/bash -c "ls -l /etc/letsencrypt/live/www.example.org/fullchain.pem"
              • Solució / Solution
                • check that the user that is running the service has permission to access dirs: /etc/letsencrypt/live, /etc/letsencrypt/archive
                • Non-root access to Letsencrypt certificates
                • install latest versions of: sofia_sip, libnice, libsrtp, libmicrohttpd (I don't know which of them solves the problem)
      • admin
        • [admin]
          admin_http = yes
          admin_port = 7088
          admin_https = yes
          admin_secure_port = 7889
    • Amazon AWS EC2
      • Janus >=v0.4.4
      • echo test demo needs a libnice version >0.1.14 (e.g. 0.1.16 master 28th June 2018 from git)
      • you need to specify NAT-1-1 with the public IP address (STUN server for Janus is not needed):
      • open ports in security group
        • TCP: 8088-8089
        • UDP: ... (same ports as defined in [media] rtp_port_range, to avoid problems with mobile networks)
        • demos
          • TCP: 8080
        • admin
          • TCP: 7088
          • TCP: 7889
  • Debug
    • Configuració / Setup
    • logs
      • tail -n 200 -f /var/log/janus/janus.log
    • admin
      • Understanding the Janus admin API
      • Admin/Monitor API
      • Configuració / Setup
        • open port 7088 in firewall (and/or AWS security group)
        • /etc/janus/janus.cfg
          • [general]
            admin_secret = mysecret
        • /etc/janus/janus.transport.http.cfg
          • [admin]
            admin_http = yes
            admin_port = 7088
        • systemctl restart janus.service
      • Ús / Usage
        • curl -X POST -H 'Content-Type: application/json' --data-binary '{"janus":"list_sessions","transaction":"1234","admin_secret":"mysecret"}' http://<janus_server_ip_address>:7088/admin
        • curl -X POST -H 'Content-Type: application/json' --data-binary '{"janus":"list_handles","transaction":"1234","admin_secret":"mysecret"}' http://<janus_server_ip_address>:7088/admin/<session>
        • curl -X POST -H 'Content-Type: application/json' --data-binary '{"janus":"handle_info","transaction":"1234","admin_secret":"mysecret"}' http://<janus_server_ip_address>:7088/admin/<session>/<handle>
      • Resposta / Response
        • Header
          info
          • janus
          • session_id
          • plugin



          Plugin specific

          plugin_specific
          ...


          Handle flags

          flags



          SDPs

          sdps



          PeerConnection

          streams
          Header
          • id
          • ready




          SSRC
          ssrc




          ICE components
          components
          • state: connecting, ready, disconnected, failed
          • local-candidates
          • remote-candidates
          • selected-pair
          • dtls
          • in-stats
          • out-stats
    • Troubleshooting

      processing




      succeeded





      server

      client


      server

      client



      admin
      tshark
      wireshark
      chrome://webrtc-internals about:webrtc
      admin
      tshark
      wireshark
      chrome://webrtc-internals about:webrtc
      ICE
      streams/components/
      • state: connecting

      filter: stun
      iceconnectionstatechange: checking
      streams/components/
      • state: ready
      • connected: xxxxx




      DTLS
      streams/components/dtls/
      • dtls-state: created
      • valid: false
      • ready: false

      filter: dtls


      streams/components/dtls/
      • dtls-state: connected
      • valid: true
      • ready: true




    • tshark
      • server
        • get destination port:
          • client: from Firefox about:webrtc "Remote candidate"
          • server /var/log/janus/janus.log: a=candidate
        • tshark -d udp.port==<destination_port>,rtp udp port <destination_port>
          • 8857 14.757747410 192.168.0.101 -> 192.168.0.108 RTP 153 PT=DynamicRTP-Type-109, SSRC=0xDE951CF3, Seq=61918, Time=1643530196 8862 14.779327125 192.168.0.101 -> 192.168.0.108 RTP 1126 PT=DynamicRTP-Type-120, SSRC=0xC0C0D699, Seq=31111, Time=617518567
            ...
        • ...
  • Janus plugins
    • For old custom plugins, use v0.1.x (instead of v0.2.x)
    • v0.1.x
      • include janus/plugin.h
    • v0.2.x
      • include janus/plugins/plugin.h
      • Migration from v0.1.x to v0.2.x:
        • ...
    • 3rd party plugins
    • Included plugins
      • streaming
        • janus.plugin.streaming.c (github)
          • Streaming API
            • CURL examples

            • request
              js
              request json
              response json
              event
              janus_...c
              synchronous
              (immediate response)
              • list
              • create
              • destroy
              • recording
              • enable
              • disable

              {
                "transaction": "21bGZvoTdbG8Ab6X",
                "janus": "message",
                "body":
              • {"request": "list"}
              • {"request": "create",
                "type": "rtp",
                "description": "First test",

                "audio_port": 8006,
                "video_port": 8004
                ...}
              }
              {
                "janus": "success",
                "session_id": 8566092198416479,
                "sender": 6220156789031370,
                "transaction": "21bGZvoTdbG8Ab6X",
                "plugindata": {
                  "plugin": "janus.plugin.streaming",
                  "data":

              • {"streaming": "list",
                      "list": [
                        {
                          "id": 1,
                          "description": "Opus/VP8 live stream coming from gstreamer",
                          "type": "live",
                          "audio_age_ms": 66673351,
                          "video_age_ms": 66673351
                        },
                        ...
                       ]
                }
              • {"streaming": "created",
                      "created": "3346122389194671",
                      "permanent": false,
                      "stream": {
                        "id": 3346122389194671,
                        "description": "First test",
                        "type": "live",
                        "is_private": false,
                        "audio_port": 8006,
                        "video_port": 8004
                      }
                }
              • ...
              }
              -
              asynchronous
              (response in an event)
              • watch
              • start
              • pause
              • switch
              • stop
              startStream
              {
                "transaction": "21bGZvoTdbG8Ab6X",
                "janus": "message",

                "body":
                  {"request": "watch",
                   "id": ...}

              • preparing
              • starting
              • started
              • stopped

              onmessage: function(msg, jsep) {
                if(jsep !== undefined && jsep !== null) {
                  streaming.createAnswer
              (...)
              },

              {
                "janus": "event",
               
              "session_id": 8566092198416479,
                "sender": 6220156789031370,
                "transaction": "21bGZvoTdbG8Ab6X",
                "plugindata": {
                  "plugin": "janus.plugin.streaming",
                  "data": {
                    "streaming": "event",
                    "result": {"status": "preparing"}
                  }
                "jsep": {
                  "type": "offer",
                  "sdp": "..."
                }
              }

              {
                "transaction": "21bGZvoTdbG8Ab6X",
                "janus": "message",
                "body":
              {"request": "start"}
                "jsep": {"type": "answer",
                         "sdp": "..."}



              onmessage: function(msg, jsep) {
                var result = msg["result"];
                if(result != undefined && result != null) {...
              }
              },

              {
                "janus": "event",...
                "plugindata": {
                  "plugin": "janus.plugin.streaming",
                  "data": {
                    "streaming": "event",
                   
              "result": {"status": "starting"}


              {
                "janus": "event",...
                "plugindata": {
                  "plugin": "janus.plugin.streaming",
                  "data": {
                    "streaming": "event",
                   
              "result": {"status": "started"}

              onremotestream: function(stream) {
                $('#videoremote').append('<video  id="remotevideo" width=320 height=240 autoplay/>');
                Janus.attachMediaStream($('#remotevideo').get(0), stream);
              },




        • config
          • /etc/janus/janus.plugin.streaming.cfg

          • config file config file
            streamer
            browser notes

            /etc/janus/
            janus.plugin.streaming.jcfg
            /etc/janus/janus.plugin.streaming.cfg (OBSOLETE)
            Chrome
            Firefox
            VP8 / Opus

            [gstreamer-sample]
            type = rtp
            id = 1
            description = Opus/VP8 live stream coming from gstreamer
            audio = yes
            video = yes
            audioport = 5002
            audiopt = 111
            audiortpmap = opus/48000/2
            videoport = 5004
            videopt = 100
            videortpmap = VP8/90000
            secret = adminpwd



            [opus-vp8-multicast]
            type = rtp
            id = 20
            description = Opus/VP8 live multicast stream
            audio = yes
            video = yes
            audioport = 5002
            audiomcast = 232.3.4.5
            audiopt = 111
            audiortpmap = opus/48000/2
            videoport = 5004
            videomcast = 232.3.4.5
            videopt = 100
            videortpmap = VP8/90000

            • streaming RTP VP8/Opus with Gstreamer
            • streaming RTP VP8/Opus with ffmpeg
              • ffmpeg -re -f lavfi -i "testsrc=size=320x180:rate=24" -f lavfi -i "sine=frequency=440:sample_rate=48000, aformat=channel_layouts=stereo" -c:v vp8 -b:v 700k -an -f rtp -payload_type 100 rtp://232.3.4.5:5004?pkt_size=1400 -c:a opus -strict -2 -b:a 64k -vn -f rtp -payload_type 111 rtp://232.3.4.5:5002?pkt_size=1400 -sdp_file /tmp/vp8_opus_multicast.sdp
            ok
            ok
            H.264
            (unicast)
            h264-sample: {
                    type = "rtp"
                    id = 10
                    description = "H.264 live stream coming from gstreamer"
                    audio = false
                    video = true
                    videoport = 8004
                    videopt = 126
                    videocodec = "h264"
                    videofmtp = "profile-level-id=42e01f;packetization-mode=1"
                    #secret = "adminpwd"
            }

            • ffmpeg -re -f lavfi -i "testsrc=size=320x180:rate=24" -c:v libx264 -b:v 700k -pix_fmt yuv420p -profile:v baseline -level:v 31 -an -f rtp -payload_type 126 rtp://127.0.0.1:8004 -sdp_file /tmp/h264.sdp
            • gst-launch-1.0 -v videotestsrc ! videoconvert ! x264enc ! video/x-h264,profile=baseline ! rtph264pay config-interval=10 pt=126 ! udpsink host=127.0.0.1 port=8004
            • gst-launch-1.0 -v videotestsrc ! video/x-raw,width=1280,height=720 ! videoconvert ! x264enc ! video/x-h264,profile=baseline ! rtph264pay config-interval=10 pt=126 ! udpsink host=127.0.0.1 port=8004

            janus.plugin.streaming.jcfg
            # All browsers also support H.264, often through Cisco's OpenH264 plugin.
            # The only profile that is definitely supported is the baseline one, which
            # means that if you try a higher one it might or might not work. No matter
            # which profile you encode, though, you can put a custom one in the SDP if
            # you override the fmtp SDP attribute via 'videofmtp'. The following is an
            # example of how to create a simple H.264 mountpoint: you can feed it via
            # an x264enc+rtph264pay pipeline in gstreamer, an ffmpeg script or other.
            #
            h264-sample: {...
            profile-level-id ffmpeg Chrome Firefox
            42e01f -profile:v baseline -level:v 31 ok ok
            4d0033 -profile:v main -bf 0 -level:v 51 ok not ok
            4de033
            -profile:v main -bf 0 -level:v 51 ok
            ok
            IMPORTANT: in order to have it working in Firefox, second byte cannot be 00 (not constrained); it must be e0 (constrained)
            H.264
            (multicast)

            [h264-multicast]
            type = rtp
            id = 10
            description = H.264 live multicast stream
            audio = no
            video = yes
            videoport = 8004

            videomcast = 232.6.7.8

            videopt = 126
            videortpmap = H264/90000
            videofmtp = profile-level-id=42e01f\;packetization-mode=1

            • ffmpeg -re -f lavfi -i "testsrc=size=320x180:rate=24" -c:v libx264 -b:v 700k -pix_fmt yuv420p -profile:v baseline -level:v 31 -an -f rtp -payload_type 126 rtp://232.6.7.8:8004 -sdp_file /tmp/h264.sdp
            • ffmpeg -re -i sintel.mp4 -an -c copy -bsf:v h264_mp4toannexb -f rtp -payload_type 126 rtp://232.6.7.8:8004?pkt_size=1400 -sdp_file /tmp/sintel.sdp
            ok
            if profile.level-id is not specified, Firefox interprets offered sdp as: a=fmtp:126 profile-level-id=420010;level-asymmetry-allowed=0;packetization-mode=1
            and Firefox response only contains VP8:
            a=rtpmap:120 VP8/90000
            It does not work.
            H.264 / Opus
            [h264-opus-multicast]
            type = rtp
            id = 4246246658112756
            description = h264_opus_multicast
            audio = yes
            audioport = 8002
            audiomcast = 232.3.4.5
            audiopt = 111
            audiortpmap = opus/48000/2
            audiofmtp = sprop-stereo=1
            video = yes
            videoport = 8004
            videomcast = 232.6.7.8
            videopt = 126
            videortpmap = H264/90000
            videofmtp = packetization-mode=1\;profile-level-id=42e01f
            data = no
            • ffmpeg -re -f lavfi -i "testsrc=size=320x180:rate=24" -f lavfi -i "sine=frequency=440:sample_rate=48000, aformat=channel_layouts=stereo" -c:v libx264 -b:v 700k -pix_fmt yuv420p -profile:v baseline -level:v 31 -an -f rtp -payload_type 126 rtp://232.6.7.8:8004?pkt_size=1400 -c:a opus -strict -2 -b:a 64k -vn -f rtp -payload_type 111 rtp://232.3.4.5:8002?pkt_size=1400 -sdp_file /tmp/h264_opus_multicast.sdp


        • Problemes / Problems
      • videoroom
      • plain RTP
  • DTLS
    • janus.cfg
      • ; Certificate and key to use for DTLS.
        [certificates]
        cert_pem = /usr/share/janus/certs/mycert.pem
        cert_key = /usr/share/janus/certs/mycert.key

        [media]
        ;dtls_mtu = 1200
    • Problems with DTLS
  • Problemes / Problems
  • Accés des de client / Access from client
    • Info
    • API
      • with curl


        • curl using janus.js
          (either from html or an intermediate js)
          general values

          janus_url_base=http://192.168.1.130:8088 janus_url_base=https://192.168.1.130:8089 janus_url=${janus_url_base}/janus
          cookies_options='-b galetes.txt -c galetes.txt'
          touch galetes.txt
          origin_url=
          https://192.168.1.130:8080
          origin_header="-H 'Origin: ${origin_url}'"
          headers=${origin_header}
          cookies_options='-b galetes.txt -c galetes.txt'
          # accept self-signed certificates
          cert_options='-k'
          transaction=1234567890

          var server = "http://192.168.1.128:8088/janus";
          Janus.init({...})
          get info

          curl -i -X GET ${cert_options} ${janus_url}/info
          check CORS

          curl -i -X OPTIONS ${cert_options} ${cookies_options} "$headers" "${janus_url}"

          get a new session

          response=$(curl -X POST ${cert_options} ${cookies_options} "$headers" -H 'Content-Type: application/json' --data "{\"janus\":\"create\",\"transaction\":\"${transaction}\"}" "${janus_url}" )
          cat galetes.txt
          session_id=$(echo "$response" | jq '.data.id')
          echo "session_id: $session_id"
          session
          _url="${janus_url_base}/janus/${session_id}"
          var janus = new Janus({...})
          attach to a plugin
          (e.g.: janus.plugin.streaming)

          plugin_name="janus.plugin.streaming"
          response=$(curl -X POST ${cert_options} ${cookies_options} "$headers" -H 'Content-Type: application/json' --data "{\"janus\":\"attach\",\"plugin\":\"${plugin_name}\",\"transaction\":\"${transaction}\"}" "${session_url}" )
          plugin_session_id=$(echo "$response" | jq '.data.id')
          plugin_url="${session_url}/${plugin_session_id}"

          janus.attach({...})
          send a message to the plugin
          (e.g.: janus.plugin.streaming)
          e.g. list of existing mountpoints (available streams) in janus.plugin.streaming message_body="{\"request\":\"list\"}"

          create new mountpoint in janus.plugin.streaming [id=...]
          admin_key=... # defined in janus.plugin.streaming.cfg: [general] admin_key
          message_body="{\"request\":\"create\",\"
          type\":\"rtp\",\"id\":99,\"description\":\"stream_description\",\"audio\":true,\"video\":true,\"audioport\":1111,\"audiopt\":111,\"audiortpmap\":\"opus/48000/2\",\"videoport\":2222,\"videopt\":100,\"videortpmap\":\"VP8/90000\",\"permanent\":false,\"admin_key\":\"${admin_key}\"}"

          destroy mountpoint in janus.plugin.streaming id=99
          admin_secret="adminpwd"
          message_body="{\"request\":\"destroy\",\"id\":${id},\"secret\":\"${admin_secret}\"}"


          (send message_body)
          curl -i -k -X POST -H 'Content-Type: application/json' --data-binary "{\"janus\":\"message\", \"transaction\":\"${transaction}\", \"body\":${message_body} }" "${plugin_url}" pluginHandle.send({...})
          keep session active.
          If not called, the session will expire after value specified in janus.cfg session_timeout (default: 60 seconds)

          rid=$(date +%s%03N)
          get_url="${session_url}?rid=${rid}&maxev=1"
          echo "url: $url"
          curl -v -i -X GET ${cert_options} ${cookies_options} "$headers" "${get_url}"
          cat galetes.txt

          close session

          curl -i -X POST ${cert_options} ${cookies_options} "$headers" -H 'Content-Type: application/json' --data "{\"janus\":\"destroy\",\"transaction\":\"${transaction}\"}" "${session_url}"
          cat galetes.txt

          janus.destroy({...})
      • with Javascript
        • janus.js
          • Janus.init()
            • debug: ...
            • dependencies: Janus.useDefaultDependencies
            • callback: function(){...}
            • var janus = new Janus({...})
              • server: ...
              • iceServers: ...
              • ipv6: ...
              • withCredentials: ...
              • max_poll_events: ...
              • destroyOnUnload: ...
              • token: ...
              • apisecret: ...
              • success: function() {...}
              • error: function(cause) {...}
              • destroyed: function() {...}
            • janus.getServer()
            • janus.isConnected()
            • janus.getSessionId()
            • janus.attach({...})
              • plugin: ...
              • opaqueId: ...
              • success: function(pluginHandle) {...}
                • pluginHandle.getId()
                • pluginHandle.getPlugin()
                • pluginHandle.send({...})
                  • message: ...
                  • ...
                • pluginHandle.createOffer(callbacks)
                  • media: {...}
                    • audioSend: true/false
                    • audioRecv: true/false
                    • audio: true/false
                    • audio: {...}
                      • deviceId: ...
                    • videoSend: true/false
                    • videoRecv: true/false
                    • video: true/false
                    • video: "lowres"/"lowres-16:9"/"stdres"/"stdres-16:9"/"hires"/"hires-16:9"
                      // 320x240/320x180/640x480/640x360/1280x720
                    • video: "screen"
                    • video: {...}
                      • deviceId: ...
                        // Janus.listDevices(callback)
                      • width: ...
                      • height: ... 
                    • data: true/false
                    • failIfNoAudio: true/false
                    • failIfNoVideo: true/false
                    • screenshareFrameRate: ...
                  • trickle: true/false
                  • stream: ...
                  • success: function(jsep) {...}
                    // got sdp
                  • error: function() {...}
                • pluginHandle.createAnswer(callbacks)
                  • (same as CreateOffer)
                  • jsep: ...
                • pluginHandle.handleRemoteJsep(callbacks)
                • pluginHandle.dtmf(parameters)
                • pluginHandle.data(parameters)
                • pluginHandle.getBitrate()
                • pluginHandle.hangup(sendRequest)
                • pluginHandle.detach(parameters)
              • error: function(cause) {...}
              • consentDialog: function(on) {...}
              • webrtcState: function() {...}
              • iceState: function() {...}
              • mediaState: function() {...}
              • slowLink: function() {...}
              • onmessage: function(msg, jsep) {...}
              • onlocalstream: function(stream) {...}
              • onremotestream: function(stream) {...}
              • ondataopen: function() {...}
              • ondata: function() {...}
              • oncleanup: function() {...}
              • detached: function() {...}
            • janus.destroy({...})
              • ...
            Janus.debug(...)
          • Exemples / Examples
            • basic session creation (all inside the document ready function)
              • <!doctype html>

                <head>
                  <meta charset="utf-8"/>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.0.0/adapter.min.js"></script>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
                  <script type="text/javascript" src="janus.js"></script>

                  <script type="text/javascript">
                   var server = "http://192.168.1.128:8088/janus";

                   $(document).ready(function() {
                       Janus.init({
                           debug: true,
                           dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
                           callback: function() {
                               // init completed

                               // create gateway session
                               var janus = new Janus(
                                   {
                                       server: server,
                                       withCredentials: true,
                                       success: function() {
                                           // gateway session created

                                           Janus.debug("---- is connected: " + janus.isConnected() );
                                           Janus.debug("---- session id: " + janus.getSessionId() );
                                       },
                                       error: function(cause) {
                                           // error when creating gateway session
                                       },
                                       destroyed: function() {
                                           // gateway session destroyed
                                       }
                                   });
                               Janus.debug("---- server: " + janus.getServer() );
                           }
                       });
                   });
                  </script>

                </head>

                <body>
                Janus minimal example.
                </body>
            • basic attach to a plugin (function plugin_client, called from document ready)
              • <!doctype html>

                <head>
                  <meta charset="utf-8"/>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.0.0/adapter.min.js"></script>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
                  <script type="text/javascript" src="janus.js"></script>

                  <script type="text/javascript">
                   /**
                    * This script could be placed in a file.
                    */
                   var plugin_client = function(params) {

                       var PLUGIN_PACKAGE = "janus.plugin.streaming";
                       var janus = null;
                       var myPluginHandle = null;

                       this.start = function() {
                           Janus.init({
                               debug: true,
                               dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
                               callback: function() {
                                   // init completed

                                   // create gateway session
                                   janus = new Janus(
                                       {
                                           server: params.server,
                                           withCredentials: true,
                                           success: function() {
                                               // gateway session created
                                               Janus.log("Janus session id: " + janus.getSessionId() );

                                               // attach to plugin
                                               janus.attach(
                                                   {
                                                       plugin: PLUGIN_PACKAGE,
                                                       success: _onAttachSuccess,
                                                       error: _onError,
                                                       onmessage: _onMessage,
                                                       onlocalstream: _onLocalStream,
                                                       onremotestream: _onRemoteStream,
                                                       oncleanup: _onCleanUp
                                                   });
                                           },
                                           error: function(cause) {
                                               // error when creating gateway session
                                           },
                                           destroyed: function() {
                                               // gateway session destroyed
                                           }
                                       });
                                   Janus.debug("Janus server: " + janus.getServer() );
                               }
                           });
                       };

                       this.stop = function() {
                           if (janus) {
                               janus.destroy();
                               janus = null;
                           } 
                       };

                       function _onAttachSuccess(pluginHandle) {
                           myPluginHandle = pluginHandle;
                           Janus.log("Plugin attached: " + myPluginHandle.getPlugin() + ", id=" + myPluginHandle.getId() );
                       }

                       function _onError(message) {
                           Janus.error(message);
                       }
                      
                       function _onMessage(msg, jsep) {
                           Janus.debug("Got a message");
                           Janus.debug(JSON.stringify(msg));
                       }

                       function _onLocalStream(stream) {
                           Janus.debug("Got a local stream");
                           Janus.debug(JSON.stringify(stream));
                       }
                      
                       function _onRemoteStream(stream) {
                           Janus.debug("Got a remote stream");
                           Janus.debug(JSON.stringify(stream));
                       }
                      
                       function _onCleanUp() {
                           Janus.log("Got a cleanup notification");
                       }
                      

                   };
                  </script>


                  <script type="text/javascript">
                   var janus_server = "http://192.168.1.128:8088/janus";

                   $(document).ready(function() {
                       client = new plugin_client(
                           {
                               server: janus_server
                           }
                       );
                       client.start();
                   });
                  </script>

                </head>

                <body>
                Janus minimal example.
                </body>
            • multiple player from streaming plugin
              • <!doctype html>

                <head>
                  <meta charset="utf-8"/>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/webrtc-adapter/6.0.0/adapter.min.js"></script>
                  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
                  <script type="text/javascript" src="janus.js"></script>

                  <script type="text/javascript">
                   /**
                    * This script could be placed in a file.
                    */
                   var plugin_client = function(params) {

                       var PLUGIN_PACKAGE = "janus.plugin.streaming";
                       var janus = null;
                       var streaming = null;

                       // visible players
                       var number_feeds = 6;
                       var feeds = [];

                       // available mountpoints in Janus streaming plugin
                       var mountpoints = [];

                       this.start = function() {
                           Janus.init({
                               debug: true,
                               dependencies: Janus.useDefaultDependencies(), // or: Janus.useOldDependencies() to get the behaviour of previous Janus versions
                               callback: function() {
                                   // init completed

                                   // create gateway session
                                   janus = new Janus(
                                       {
                                           server: params.server,
                                           withCredentials: true,
                                           success: function() {
                                               // gateway session created
                                               Janus.log("## Janus session id: " + janus.getSessionId() );

                                               // attach to plugin
                                               janus.attach(
                                                   {
                                                       plugin: PLUGIN_PACKAGE,
                                                       success: _onAttachSuccess,
                                                       error: _onError,
                                                       onmessage: _onMessage,
                                                       onlocalstream: _onLocalStream,
                                                       onremotestream: _onRemoteStream,
                                                       oncleanup: _onCleanUp
                                                   });
                                           },
                                           error: function(cause) {
                                               // error when creating gateway session
                                           },
                                           destroyed: function() {
                                               // gateway session destroyed
                                           }
                                       });
                                   Janus.debug("## Janus server: " + janus.getServer() );
                               }
                           });
                       };

                       this.stop = function() {
                           if (janus) {
                               janus.destroy();
                               janus = null;
                           }  
                       };

                       function _onAttachSuccess(pluginHandle) {
                           streaming = pluginHandle;
                           Janus.log("## Plugin attached: " + streaming.getPlugin() + ", id=" + streaming.getId() );
                           updateStreamsList();
                       }

                       function _onError(message) {
                           Janus.error(message);
                       }
                       
                       function _onMessage(msg, jsep) {
                           Janus.debug("## Got a message");
                           Janus.debug(msg);
                           if(jsep !== undefined && jsep !== null) {
                               Janus.debug("## Handling SDP as well...");
                               Janus.debug(jsep);
                           }
                       }

                       function _onLocalStream(stream) {
                           Janus.debug("## Got a local stream");
                           Janus.debug(JSON.stringify(stream));
                       }
                       
                       function _onRemoteStream(stream) {
                           Janus.debug("## Got a remote stream");
                           Janus.debug(JSON.stringify(stream));
                       }
                       
                       function _onCleanUp() {
                           Janus.log("## Got a cleanup notification");
                       }
                       

                       function updateStreamsList() {
                           // get list of available mountpoints in streaming plugin
                           var body = { "request": "list" };
                           streaming.send(
                               {
                                   "message": body,
                                   success: function(result) {
                                       if(result === null || result === undefined) {
                                           bootbox.alert("Got no response to our query for available streams");
                                           return;
                                       }
                                       if(result["list"] !== undefined && result["list"] !== null) {
                                           mountpoints = result["list"];
                                           Janus.debug(mountpoints);
                                           populateAll(mountpoints);
                                       }
                                   }
                               });
                       }

                       function populateAll (mountpoints) {
                           // start a player for each mountpoint
                           Janus.log("######## [populateAll]");
                           for(var m in mountpoints) {
                               var mountpoint_id = mountpoints[m]["id"];
                               var type = mountpoints[m]["type"];
                               var description = mountpoints[m]["description"];
                               newRemoteFeed(mountpoint_id, type, description);
                           }
                       }

                       function newRemoteFeed(mountpoint_id, type, description) {
                           // start a player
                           Janus.log("######## [newRemoteFeed] mountpoint_id: "+ mountpoint_id + "; type: " + type + "; description: " + description );
                           var remoteFeed = null;
                           janus.attach(
                               {
                                   plugin: "janus.plugin.streaming",
                                   success: function(pluginHandle) {
                                       remoteFeed = pluginHandle;               
                                       Janus.log("######## [newRemoteFeed] Plugin attached! (" + remoteFeed.getPlugin() + ", id=" + remoteFeed.getId() + ")");

                                       // assign a player
                                       for(var i=1;i<6;i++) {
                                           if(feeds[i] === undefined || feeds[i] === null) {
                                               feeds[i] = remoteFeed;
                                               remoteFeed.rfindex = i;
                                               break;
                                           }
                                       }

                                       // send watch request
                                       var body = { "request": "watch", "id": mountpoint_id };
                                       remoteFeed.send({"message": body});
                                   },
                                   error: function(error) {},
                                   onmessage: function(msg, jsep) {
                                       // received a message
                                       Janus.debug("######## [newRemoteFeed.onmessage] Got a message (mountpoint_id: " + mountpoint_id + ")");
                                       Janus.debug(msg);

                                       // regular message
                                       var result = msg["result"];
                                       if(result != undefined && result != null) {
                                           Janus.debug(result);
                                           if(result["status"] !== undefined && result["status"] !== null) {
                                               var status = result["status"];
                                               if(status === 'starting')
                                                   Janus.debug("######## [newRemoteFeed.onmessage] Starting, please wait...");
                                               else if(status === 'started')
                                                   Janus.debug("######## [newRemoteFeed.onmessage] Started");
                                               else if(status === 'stopped')
                                                   Janus.debug("######## [newRemoteFeed.onmessage] Stopped");
                                           } else if(msg["streaming"] === "event") {
                                               // Is simulcast in place?
                                               var substream = result["substream"];
                                               var temporal = result["temporal"];
                                               if((substream !== null && substream !== undefined) || (temporal !== null && temporal !== undefined)) {
                                                   if(!simulcastStarted) {
                                                       simulcastStarted = true;
                                                       addSimulcastButtons();
                                                   }
                                                   // We just received notice that there's been a switch, update the buttons
                                                   updateSimulcastButtons(substream, temporal);
                                               }
                                           }
                                       } else if(msg["error"] !== undefined && msg["error"] !== null) {
                                           Janus.error("######## [newRemoteFeed] Error: ", msg["error"]);
                                       }

                                       // message with an offer sdp
                                       if(jsep !== undefined && jsep !== null) {
                                           Janus.debug("######## [newRemoteFeed.onmessage] Received an offer sdp (mountpoint_id: " + mountpoint_id + ")");
                                           Janus.debug(jsep);
                                           // create an answer with an sdp
                                           remoteFeed.createAnswer(
                                               {
                                                   jsep: jsep,
                                                   media: { audioSend: false, videoSend: false },       // We want recvonly audio/video
                                                   success: function(jsep) {
                                                       Janus.debug("######## [newRemoteFeed.onmessage] Sending an answer sdp (mountpoint_id: " + mountpoint_id + ")");
                                                       Janus.debug(jsep);
                                                       var body = { "request": "start" };
                                                       remoteFeed.send({"message": body, "jsep": jsep});
                                                       //$('#watch').html("Stop").removeAttr('disabled').click(stopStream);
                                                   },
                                                   error: function(error) {
                                                       Janus.error("WebRTC error:", error);
                                                       bootbox.alert("WebRTC error... " + JSON.stringify(error));
                                                   }
                                               });

                                       }
                                   },
                                   onremotestream: function(stream) {
                                       // a remote stream is available
                                       Janus.debug("######## [newRemoteFeed.onremotestream] Remote stream is available (mountpoint_id: " + mountpoint_id + ")");

                                       // first time?
                                       if($('#remotevideo'+remoteFeed.rfindex).length > 0) {
                                           // Been here already: let's see if anything changed
                                           Janus.debug("######## [newRemoteFeed.onremotestream] Been there already");
                                           var videoTracks = stream.getVideoTracks();
                                           if(videoTracks && videoTracks.length > 0 && !videoTracks[0].muted) {
                                               Janus.debug(videoTracks);
                                               //$('#novideo'+remoteFeed.rfindex).remove();
                                               //if($("#remotevideo"+remoteFeed.rfindex).get(0).videoWidth)
                                               //          $('#remotevideo'+remoteFeed.rfindex).show();
                                           }
                                           return;
                                       }

                                       // create the html5 player
                                       $('#videoremote' + remoteFeed.rfindex).append( '<h2>' + description + '</h2> <video class="rounded centered" id="remotevideo' + remoteFeed.rfindex + '" width=320 height=240 controls autoplay/><hr>');

                                       // attach the remote stream to the html5 video player
                                       Janus.attachMediaStream($('#remotevideo' + remoteFeed.rfindex).get(0), stream);
                                   },
                                   oncleanup: function() {}
                               }
                           );
                       }

                   };
                  </script>


                  <script type="text/javascript">
                   // var janus_server = "https://192.168.1.114:8089/janus";
                   var janus_server = null;
                   if(window.location.protocol === 'http:')
                       janus_server = "http://" + window.location.hostname + ":8088/janus";
                   else
                       janus_server = "https://" + window.location.hostname + ":8089/janus";
                   
                   $(document).ready(function() {
                       client = new plugin_client(
                           {
                               server: janus_server
                           }
                       );
                       client.start();
                   });
                  </script>

                </head>

                <body>
                  <h1>Multiple streams</h1>
                  <div id="logs"></div>
                  <div id="videoremote1"></div>
                  <div id="videoremote2"></div>
                  <div id="videoremote3"></div>
                  <div id="videoremote4"></div>
                  <div id="videoremote5"></div>
                  <div id="videoremote6"></div>
                </body>
  • Demos (test web server)
    • Available demos
      • Streaming
        • Problemes / Problems
          • llista buida al desplegable «Stream list»
            • Solució / Solution
              • restart janus service
      • ...
    • http-server (NodeJS)
      • Install NodeJS via nvm, and then:
        • install http server
          npm install -g http-server
      • configure firewall
        • sudo systemctl start firewalld.service
          sudo firewall-cmd --permanent --zone=public --add-port=8080/tcp
          sudo firewall-cmd --reload
      • install html dir into /usr/share/janus/demos/
        • cd janus-gateway
          sudo make install html
      • non-secure
        • run http-server
          • nvm use default
            http-server /usr/share/janus/demos
        • check it:
          • http://<your_demos_server_ip>:8080/
          • http://<your_demos_server_ip>:8080/demos/
      • secure
        • server
          • nvm use default
            http-server --ssl --cert /usr/share/janus/certs/mycert.pem --key /usr/share/janus/certs/mycert.key /usr/share/janus/demos
        • client
        • Problemes / Problems
          • Probably a network error, is the gateway down?: [object Object]
            • Solució / Solution:
              • Des del navegador, accepteu manualment el certificat autosignat / From your browser, manually accept self-signed certificate in
                • https://<your_janus_server_ip>:8089/
              • Reviseu l'apartat [cors] del fitxer /etc/janus/janus.cfg
    • nginx
      • server
        • Janus through Nginx
          • Deploying Janus behind a web frontend
          • /etc/nginx/default.d/janus.conf
            • # janus (webrtc server)
              location /janus {
                  proxy_pass http://127.0.0.1:8088/janus;
              }
          • SELinux
            • setsebool -P httpd_can_network_connect 1
            • chcon -u system_u -t httpd_config_t /etc/nginx/default.d/janus.conf
        • access to demos
          • /etc/nginx/default.d/janus_demos.conf
            • # janus demos (webrtc server)
              location /demos {
                   root /usr/share/janus;
              }
          • SELinux
            • chcon -u system_u -t httpd_config_t /etc/nginx/default.d/janus_demos.conf
        • demos must point to Janus through nginx (instead of ports 8088/8089):
          • cd /usr/share/demos
          • sed -i '/var janus = null;/ i var server = "/janus";' *.js
        • https
          • generate self-signed certificate and key (/etc/pki/nginx/server.crt, /etc/pki/nginx/private/server.key)
            • mkdir -p /etc/pki/nginx/private
            • cd /etc/pki/nginx
            • openssl req -new -nodes -keyout private/server.key -sha256 -x509 -out server.crt
          • /etc/nginx/conf.d/ssl_8080.conf
            • # Settings for a TLS enabled server.

              server {
                  listen       8080 ssl http2 default_server;
                  listen       [::]:8080 ssl http2 default_server;
                  server_name  _;
                  root         /usr/share/nginx/html;

                  ssl_certificate "/etc/pki/nginx/server.crt";
                  ssl_certificate_key "/etc/pki/nginx/private/server.key";
                  ssl_session_cache shared:SSL:1m;
                  ssl_session_timeout  10m;
                  ssl_ciphers HIGH:!aNULL:!MD5;
                  ssl_prefer_server_ciphers on;

                  # Load configuration files for the default server block.
                  include /etc/nginx/default.d/*.conf;

                  location / {
                  }

                  error_page 404 /404.html;
                      location = /40x.html {
                  }

                  error_page 500 502 503 504 /50x.html;
                      location = /50x.html {
                  }
              }
          • SELinux
            • chcon -u system_u -t httpd_config_t /etc/nginx/default.d/ssl_8080.conf
        • sudo systemctl start nginx.service
      • client
        • https://<your_demos_server_ip>/janus/info
          • Problemes / Problems
            • on server logs: connect() to 127.0.0.1:8088 failed (13: Permission denied) while connecting to upstream, client: ...
        • https://<your_demos_server_ip>/demos

UV4L

  • Installation for ARM (Raspberry Pi)
    1. curl http://www.linux-projects.org/listing/uv4l_repo/lrkey.asc | sudo apt-key add -
    2. /etc/apt/sources.list
      • deb http://www.linux-projects.org/listing/uv4l_repo/raspbian/ jessie main
    3. sudo apt-get update
    4. sudo apt-get install uv4l uv4l-raspicam
    5. sudo apt-get install uv4l-raspicam-extras
    6. sudo service uv4l_raspicam restart
      • /etc/uv4l/uv4l-raspicam.conf
    7. Test
      • dd if=/dev/video0 of=snapshot_$(date '+%Y-%m-%dT%H%M%SZ' --utc).jpeg bs=11M count=1
      • gpicview snapshot_...jpeg
    8. pkill uv4l
    9. optional
  • Usage
    • uv4l [ uv4l-options ] [ –enable-server option ] [ –server-option ‘option=value’ [ … ] ]

    • needed packages
      command
      service
      config file
      client
      core
      uv4l
      uv4l [ uv4l-options ]


      raspicam
      uv4l-raspicam

      /etc/uv4l/uv4l-raspicam.conf
      uv4l-raspicam-extras
      (/etc/init.d/uv4l_raspicam)
      sudo systemctl start uv4l_raspicam.service
      sudo service uv4l_raspicam start


      streaming server (RESTful API)
      uv4l-server
      • uv4l [ uv4l-options ] --enable-server=yes [--server-option ‘option=value’ [ … ] ]
      • uv4l --driver raspicam --enable-server=yes --auto-video_nr=yes
      • /usr/bin/uv4l -k --sched-fifo --mem-lock --config-file=/etc/uv4l/uv4l-raspicam.conf --driver raspicam --driver-config-file=/etc/uv4l/uv4l-raspicam.conf --enable-server=yes --server-option=--editable-config-file=/etc/uv4l/uv4l-raspicam.conf


      http://<raspberrypi>:8080/
      http://<raspberrypi>:8080/stream
      http://<raspberrypi>:8080/stream/webrtc
      • create a self-signed certificate
      • uv4l --driver=raspicam --auto-video_nr=yes --server-option=--use-ssl=yes --server-option=--ssl-private-key-file=/home/pi/192.168.1.139.key --server-option=--ssl-certificate-file=/home/pi/192.168.1.139.crt


      https://<raspberrypi>:8080/
  • debug
    • tail -f /var/log/syslog
  • WebRTC extension (two-way audio/video)
  • Janus Gateway (join a room with Janus)

http://www.francescpinyol.cat/webrtc.html
Primera versió: / First version: 10.XI.2017
Darrera modificació: 26 de gener de 2024 / Last update: 26th July 2024

Valid HTML 4.01!

Cap a casa / Back home