Post

RTSP Video Streaming over HTTPS

RTSP Video Streaming over HTTPS

RTSPtoWeb converts RTSP video streams into formats that can be consumed in a web browser, such as MSE, WebRTC, or HLS. It does not require any additional software and can pull RTSP streams from a camera and publish them over HTTPS.

Why RTSPtoWeb?

I tested several open-source tools to publish video streams over HTTPS using an HIK-VISION IP camera. The results are as follows:

ToolsLatency (s)
Hikvisin portal0.5
FFPlay2.4
RTSPtoWeb1.2
SRS WHEP2.3
SRS HTTP-FLV4.7

In comparison, RTSPtoWeb has a lower latency than the other tools and is directly published over HTTPS behind a reverse proxy.

Note: Video source must be h264 encoded for max compatibility. Set the camera to encode h264 at source so transcoding can be avoided later.

Deployment

  • Docker compose file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# Filename: /srv/livevideo.example.com/docker-compose.yaml
# Purpose: To publish RTSP video stream over HTTPS

name: livevideo-example-com

services:
  rtsp-to-web:
    image: ghcr.io/deepch/rtsptoweb:latest
    container_name: livevideo-rtsp-to-web.example.com
    restart: always

    volumes:
      - ./config.json:/config/config.json

  livevideo:
    image: nginx:latest
    container_name: livevideo.example.com

    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro

    labels:
      - com.centurylinklabs.watchtower.enable=true
      - traefik.enable=true
      - traefik.http.routers.livevideo.rule=Host(`livevideo.example.com`)
      - traefik.http.routers.livevideo.tls=true
      - traefik.http.routers.livevideo.tls.certresolver=lets-encrypt

    depends_on:
      - rtsp-to-web
  • RTSPtoWeb Configuration

Filename: /srv/livevideo.example.com/config.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{
  "channel_defaults": {
    "on_demand": true
  },
  "server": {
    "debug": true,
    "http_debug": false,
    "http_demo": true,
    "http_dir": "",
    "http_login": "api-username",
    "http_password": "api-password",
    "http_port": ":8083",
    "https": false,
    "https_auto_tls": false,
    "https_auto_tls_name": "",
    "https_cert": "",
    "https_key": "",
    "https_port": "",
    "ice_credential": "",
    "ice_servers": [],
    "ice_username": "",
    "log_level": "info",
    "rtsp_port": ":5541",
    "token": {
      "backend": "",
      "enable": false
    },
    "webrtc_port_max": 0,
    "webrtc_port_min": 0
  },
  "streams": {
    "mystream": {
      "channels": {
        "0": {
          "name": "camera-1",
          "url": "rtsp://username:password@ip"
        },
        "1": {
          "name": "camera-2",
          "url": "rtsp://username:password@ip"
        }
      },
      "name": "Live Video Stream"
    }
  }
}
  • Nginx Configuration

Filename: /srv/livevideo.example.com/nginx.conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Filename: /etc/nginx/nginx.conf

events {
  worker_connections 1024;
}

http {
    server {
        listen 80;

        include /etc/nginx/mime.types;

        location / {
          root /usr/share/nginx/html;
          index index.html;

          try_files $uri /index.html;
          add_header 'Access-Control-Allow-Origin' '*';
        }

        location /stream/ {
          proxy_pass http://rtsp-to-web:8083;
          proxy_http_version 1.1;

          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        }
    }
}
  • HTML Video Player

Filename: /srv/livevideo.example.com/html/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <title>RTSPtoWeb HLS-LL</title>
</head>

<body>
  <h1>RTSPtoWeb HLS-LL</h1>
  <div>
    <p></p>
    <video id="hlsll-video" autoplay muted playsinline controls style="width: 100%; height: 100%;"></video>
  </div>

  <script>
    document.addEventListener('DOMContentLoaded', function () {
      const videoEl = document.querySelector('#hlsll-video')

      var path = window.location.pathname;
      var parts = path.split('/'); // livevideo.example.com/mystream/channel/0

      var url = window.location.protocol + '//' + window.location.hostname;
      var stream_name = parts[parts.length - 3] + '/';
      var channel = parts[parts.length - 2] + '/';
      var channel_no = parts[parts.length - 1];

      // https://livevideo.example.com/stream/{0}/{1}/{2}/hlsll/live/index.m3u8
      var stream_url = url + '/stream/' + stream_name + channel + channel_no + '/hlsll/live/index.m3u8';

      if (Hls.isSupported()) {
        const hls = new Hls()
        hls.loadSource(stream_url)
        hls.attachMedia(videoEl)

      } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        videoEl.src = hlsllUrl
        videoEl.addEventListener('loadedmetadata', function () {
          videoEl.play();
        });
      }
    })
  </script>

  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>

</body>

</html>

Note: Here Traefik is used as reverse proxy. Traefik docker labels are attached to the nginx container to publish behind fully qualified domain name.

Now, the video stream of Camera 1 & Camera 2 is available in https://livevideo.example.com/mystream/channel/0 and https://livevideo.example.com/mystream/channel/1

Reference:

  1. GitHub Docs: https://github.com/deepch/RTSPtoWeb
  2. RTSPtoWeb API Docs: https://github.com/deepch/RTSPtoWeb/blob/master/docs/api.md
This post is licensed under CC BY 4.0 by the author.