diff --git a/Dockerfile.example b/Dockerfile.example index 7cd1f06a..4393ac6e 100644 --- a/Dockerfile.example +++ b/Dockerfile.example @@ -2,13 +2,13 @@ ARG UBUNTU_RELEASE=20.04 ARG GSTREAMER_BASE_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gstreamer ARG GSTREAMER_BASE_IMAGE_RELEASE=master -ARG PY_BUILD_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/py-build:latest -ARG WEB_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gst-web:latest +ARG PY_BUILD_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/py-build:master +ARG WEB_IMAGE=ghcr.io/selkies-project/selkies-gstreamer/gst-web:master FROM ${GSTREAMER_BASE_IMAGE}:${GSTREAMER_BASE_IMAGE_RELEASE}-ubuntu${UBUNTU_RELEASE} as selkies-gstreamer FROM ${PY_BUILD_IMAGE} as selkies-build FROM ${WEB_IMAGE} as selkies-web FROM ubuntu:${UBUNTU_RELEASE} -ARG UBUNTU_RELEASE=20.04 +ARG UBUNTU_RELEASE LABEL maintainer "https://github.com/danisla" @@ -20,7 +20,6 @@ RUN \ python3-dev \ python3-gi \ python3-setuptools \ - python3-tk \ python3-wheel \ tzdata \ sudo \ @@ -45,6 +44,7 @@ RUN \ libpangocairo-1.0-0 \ libgirepository1.0-dev \ libjpeg-dev \ + libvpx-dev \ zlib1g-dev \ x264 \ git && \ @@ -98,7 +98,7 @@ RUN \ # Install selkies-gstreamer Python app ARG PYPI_PACKAGE=selkies_gstreamer -ARG PACKAGE_VERSION=1.0.0.dev0 +ARG PACKAGE_VERSION=0.0.0.dev0 COPY --from=selkies-build /opt/pypi/dist/${PYPI_PACKAGE}-${PACKAGE_VERSION}-py3-none-any.whl . RUN pip3 install /opt/${PYPI_PACKAGE}-${PACKAGE_VERSION}-py3-none-any.whl @@ -113,6 +113,8 @@ RUN echo 'export DISPLAY=:0' \ >> /etc/bash.bashrc && \ echo 'export GST_DEBUG=*:2' \ >> /etc/bash.bashrc && \ + echo 'export GSTREAMER_PATH=/opt/gstreamer' \ + >> /etc/bash.bashrc && \ echo 'source /opt/gstreamer/gst-env' \ >> /etc/bash.bashrc @@ -121,15 +123,16 @@ RUN echo "#!/bin/bash\n\ export DISPLAY=:0\n\ export GST_DEBUG=*:2\n\ export PULSE_SERVER=127.0.0.1:4713\n\ +export GSTREAMER_PATH=/opt/gstreamer\n\ source /opt/gstreamer/gst-env\n\ Xvfb -screen :0 8192x4096x24 +extension RANDR +extension GLX +extension MIT-SHM -nolisten tcp -noreset -shmem 2>&1 >/tmp/Xvfb.log &\n\ until [[ -S /tmp/.X11-unix/X0 ]]; do sleep 1; done && echo 'X Server is ready'\n\ sudo /usr/bin/pulseaudio -k >/dev/null 2>&1\n\ sudo /usr/bin/pulseaudio --daemonize --system --verbose --log-target=file:/tmp/pulseaudio.log --realtime=true --disallow-exit -L 'module-native-protocol-tcp auth-ip-acl=127.0.0.0/8 port=4713 auth-anonymous=1'\n\ -[[ \${START_XFCE4:-true} == true ]] && xfce4-session &\n\ +[[ \${START_XFCE4:-true} == true ]] && rm -rf ~/.config/xfce4 && xfce4-session &\n\ export WEBRTC_ENCODER=\${WEBRTC_ENCODER:-x264enc}\n\ export WEBRTC_ENABLE_RESIZE=\${WEBRTC_ENABLE_RESIZE:-true}\n\ -export JSON_CONFIG=/tmp/selkies.json\n\ +export JSON_CONFIG=/tmp/rtc.json\n\ echo '{}' > \$JSON_CONFIG\n\ selkies-gstreamer-resize 1280x720\n\ selkies-gstreamer\n\ @@ -169,11 +172,13 @@ RUN groupadd -g 1000 user && \ echo "user:${PASSWD}" | chpasswd && \ ln -snf "/usr/share/zoneinfo/$TZ" /etc/localtime && echo "$TZ" > /etc/timezone +# Prevent dialogs at Xfce4 desktop environment start +RUN cp -rf /etc/xdg/xfce4/panel/default.xml /etc/xdg/xfce4/xfconf/xfce-perchannel-xml/xfce4-panel.xml + USER user +ENV USER=user +WORKDIR /home/user RUN touch ${HOME}/.sudo_as_admin_successful -# Set default icewm theme -RUN mkdir -p ${HOME}/.icewm && echo 'Theme="NanoBlue/default.theme"' > ${HOME}/.icewm/theme - ENTRYPOINT ["/tini", "--"] CMD ["/entrypoint.sh"] diff --git a/README.md b/README.md index 74b858af..64b1d047 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ Fourth, `selkies-gstreamer` is easy to use and expand to various usage cases, at ## How do I get started? -Three components are required to run `selkies-gstreamer`: the [standalone build of GStreamer](addons/gstreamer) with the most recent version, the [Python package](src/selkies_gstreamer) including the signaling server, and the [HTML5 web interface](addons/gst-web). Currently, Ubuntu 18.04, 20.04, 22.04 are supported, but other operating systems will also work (contributions for build workflows for more operating systems are welcome). +Three components are required to run `selkies-gstreamer`: the [standalone build of GStreamer](addons/gstreamer) with the most recent version, the [Python package](src/selkies_gstreamer) including the signaling server, and the [HTML5 web interface](addons/gst-web). Currently, Ubuntu 18.04 (Mint 19), 20.04 (Mint 20), 22.04 (Mint 21) are supported, but other operating systems should also work if using your own GStreamer build of the newest version (contributions for build workflows of more operating systems are welcome). All three of the components are built and packaged [every release](https://github.com/selkies-project/selkies-gstreamer/releases). In addition, every latest commit gets built and is made available in container forms [`ghcr.io/selkies-project/selkies-gstreamer/gstreamer`](https://github.com/selkies-project/selkies-gstreamer/pkgs/container/selkies-gstreamer%2Fgstreamer), [`ghcr.io/selkies-project/selkies-gstreamer/py-build`](https://github.com/selkies-project/selkies-gstreamer/pkgs/container/selkies-gstreamer%2Fpy-build), and [`ghcr.io/selkies-project/selkies-gstreamer/gst-web`](https://github.com/selkies-project/selkies-gstreamer/pkgs/container/selkies-gstreamer%2Fgst-web). @@ -36,17 +36,17 @@ Example Google Compute Engine/Google Kubernetes Engine deployment configurations **NOTE: You will need to use an external STUN/TURN server capable of `srflx` or `relay` type ICE connections if you use this in a container WITHOUT host networking (add `--network=host` to the Docker command to enable host networking and work around this requirement if your server is not behind NAT). Follow the instructions from [Using a TURN server](#using-a-turn-server) in order to make the container work using an external TURN server.** -An example image [`ghcr.io/selkies-project/selkies-gstreamer/gst-py-example`](https://github.com/selkies-project/selkies-gstreamer/pkgs/container/selkies-gstreamer%2Fgst-py-example) from the base [Dockerfile](./Dockerfile.example). +An example image [`ghcr.io/selkies-project/selkies-gstreamer/gst-py-example`](https://github.com/selkies-project/selkies-gstreamer/pkgs/container/selkies-gstreamer%2Fgst-py-example) from the base [example Dockerfile](./Dockerfile.example) is available. -Running the docker container built from the [`Dockerfile.example`](./Dockerfile.example), then connect to port **8080** of your Docker host to access the web interface: +Run the docker container built from the [`Dockerfile.example`](./Dockerfile.example), then connect to port **8080** of your Docker host to access the web interface (**replace `latest` to `master` for the development build instead of the release build, and choose the Ubuntu versions `18.04`, `20.04`, or `22.04`**): ```bash -docker run --name selkies -it --rm -p 8080:8080 ghcr.io/selkies-project/selkies-gstreamer/gst-py-example:latest +docker run --name selkies -it --rm -p 8080:8080 ghcr.io/selkies-project/selkies-gstreamer/gst-py-example:latest-ubuntu20.04 ``` Repositories [`selkies-vdi`](https://github.com/selkies-project/selkies-vdi) or [`selkies-examples`](https://github.com/selkies-project/selkies-examples) from the [Selkies Project](https://github.com/selkies-project) provide containerized virtual desktop infrastructure (VDI) templates. -[`docker-nvidia-glx-desktop`](https://github.com/ehfd/docker-nvidia-glx-desktop) and [`docker-nvidia-egl-desktop`](https://github.com/ehfd/docker-nvidia-egl-desktop) are expandable ready-to-go zero-configuration containerized remote desktop implementations of `selkies-gstreamer` supporting hardware acceleration on NVIDIA and other GPUs. +[`docker-nvidia-glx-desktop`](https://github.com/ehfd/docker-nvidia-glx-desktop) and [`docker-nvidia-egl-desktop`](https://github.com/ehfd/docker-nvidia-egl-desktop) are expandable ready-to-go zero-configuration batteries-included containerized remote desktop implementations of `selkies-gstreamer` supporting hardware acceleration on NVIDIA and other GPUs. ### Install the packaged version on a standalone machine or cloud instance @@ -55,19 +55,19 @@ Repositories [`selkies-vdi`](https://github.com/selkies-project/selkies-vdi) or 1. Install the dependencies, for Ubuntu or Debian-based distros run this command: ```bash -sudo apt-get update && sudo apt-get install --no-install-recommends -y adwaita-icon-theme-full build-essential python3-pip python3-dev python3-gi python3-setuptools python3-tk python3-wheel tzdata sudo udev xclip x11-utils xdotool wmctrl jq gdebi-core x11-xserver-utils xserver-xorg-core libopus0 libgdk-pixbuf2.0-0 libsrtp2-1 libxdamage1 libxml2-dev libwebrtc-audio-processing1 libcairo-gobject2 pulseaudio libpulse0 libpangocairo-1.0-0 libgirepository1.0-dev libjpeg-dev zlib1g-dev x264 +sudo apt-get update && sudo apt-get install --no-install-recommends -y adwaita-icon-theme-full build-essential python3-pip python3-dev python3-gi python3-setuptools python3-wheel tzdata sudo udev xclip x11-utils xdotool wmctrl jq gdebi-core x11-xserver-utils xserver-xorg-core libopus0 libgdk-pixbuf2.0-0 libsrtp2-1 libxdamage1 libxml2-dev libwebrtc-audio-processing1 libcairo-gobject2 pulseaudio libpulse0 libpangocairo-1.0-0 libgirepository1.0-dev libjpeg-dev libvpx-dev zlib1g-dev x264 ``` -Additionally, install `xcvt` if using Ubuntu 22.04 or an equivalent version of another operating system: +Additionally, install `xcvt` if using Ubuntu 22.04 (Mint 21) or an equivalent version of another operating system: ```bash sudo apt-get update && sudo apt-get install --no-install-recommends -y xcvt ``` -2. Unpack the GStreamer components of `selkies-gstreamer` (fill in `SELKIES_VERSION` and `OS_VERSION`), using your own GStreamer build may work **as long as it is the most recent version**, but is not guaranteed: +2. Unpack the GStreamer components of `selkies-gstreamer` (fill in `SELKIES_VERSION` and `UBUNTU_RELEASE`), using your own GStreamer build may work **as long as it is the most recent version with the required plugins included**: ```bash -cd /opt && curl -fsSL https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies-gstreamer-v${SELKIES_VERSION}-${OS_VERSION}.tgz | sudo tar -zxf - +cd /opt && curl -fsSL https://github.com/selkies-project/selkies-gstreamer/releases/download/v${SELKIES_VERSION}/selkies-gstreamer-v${SELKIES_VERSION}-ubuntu${UBUNTU_RELEASE}.tgz | sudo tar -zxf - ``` This will install the GStreamer components to the default directory of `/opt/gstreamer`. If you are unpacking to a different directory, make sure to set the directory to the environment variable `GSTREAMER_PATH`. @@ -98,7 +98,7 @@ cd /usr/local/cuda/lib64 && sudo find . -maxdepth 1 -type l -name "*libnvrtc.so. export DISPLAY=:0 export GST_DEBUG=*:2 export PULSE_SERVER=127.0.0.1:4713 -# Initialize the GStreamer environment after setting GSTREAMER_PATH to the path of your gstreamer directory +# Initialize the GStreamer environment after setting GSTREAMER_PATH to the path of your GStreamer directory export GSTREAMER_PATH=/opt/gstreamer source /opt/gstreamer/gst-env # Start a virtual X server, skip this line if an X server already exists or you are already using a display @@ -108,8 +108,8 @@ until [[ -S /tmp/.X11-unix/X0 ]]; do sleep 1; done && echo 'X Server is ready' # Initialize PulseAudio, TCP interface to port 4713 must be configured if using a separate setup sudo /usr/bin/pulseaudio -k >/dev/null 2>&1 sudo /usr/bin/pulseaudio --daemonize --system --verbose --log-target=file:/tmp/pulseaudio.log --realtime=true --disallow-exit -L 'module-native-protocol-tcp auth-ip-acl=127.0.0.0/8 port=4713 auth-anonymous=1' -# Replace this line with your desktop environment session, use VirtualGL `vglrun` here if needed -icewm-session & +# Replace this line with your desktop environment session or skip this line if already running, use VirtualGL `vglrun` here if needed +[[ "${START_XFCE4:-true}" == "true" ]] && rm -rf ~/.config/xfce4 && xfce4-session & # Write Progressive Web App (PWA) config. export PWA_APP_NAME="Selkies WebRTC" export PWA_APP_SHORT_NAME="selkies" @@ -136,47 +136,46 @@ selkies-gstreamer & ### Install the latest build on a standalone machine or cloud instance -Docker (or an equivalent) is required if you are to use builds from the latest commit. Refer to the above section for more granular informations. This method can be also used when building a new container image with the `FROM [--platform=] [AS ]` instruction instead of using the `docker` CLI. +Docker (or an equivalent) is required if you are to use builds from the latest commit. Refer to the above section for more granular informations. This method can be also used when building a new container image with the `FROM [--platform=] [AS ]` and `COPY [--from=] ` instruction instead of using the `docker` CLI. **Change `master` to `latest` if you want the latest release version instead of the latest development version.** **NOTE: You will need to use an external STUN/TURN server capable of `srflx` or `relay` type ICE connections if both your server and client have ports closed or are under a restrictive firewall. Either open the TCP and UDP port ranges 49152-65535 of your server, or follow the instructions from [Using a TURN server](#using-a-turn-server) in order to make the container work using an external TURN server.** 1. Install the dependencies, for Ubuntu or Debian-based distros run this command: ```bash -sudo apt-get update && sudo apt-get install --no-install-recommends -y adwaita-icon-theme-full build-essential python3-pip python3-dev python3-gi python3-setuptools python3-tk python3-wheel tzdata sudo udev xclip x11-utils xdotool wmctrl jq gdebi-core x11-xserver-utils xserver-xorg-core libopus0 libgdk-pixbuf2.0-0 libsrtp2-1 libxdamage1 libxml2-dev libwebrtc-audio-processing1 libcairo-gobject2 pulseaudio libpulse0 libpangocairo-1.0-0 libgirepository1.0-dev libjpeg-dev zlib1g-dev x264 +sudo apt-get update && sudo apt-get install --no-install-recommends -y adwaita-icon-theme-full build-essential python3-pip python3-dev python3-gi python3-setuptools python3-wheel tzdata sudo udev xclip x11-utils xdotool wmctrl jq gdebi-core x11-xserver-utils xserver-xorg-core libopus0 libgdk-pixbuf2.0-0 libsrtp2-1 libxdamage1 libxml2-dev libwebrtc-audio-processing1 libcairo-gobject2 pulseaudio libpulse0 libpangocairo-1.0-0 libgirepository1.0-dev libjpeg-dev libvpx-dev zlib1g-dev x264 ``` -Additionally, install `xcvt` if using Ubuntu 22.04 or an equivalent version of another operating system: +Additionally, install `xcvt` if using Ubuntu 22.04 (Mint 21) or an equivalent version of another operating system: ```bash sudo apt-get update && sudo apt-get install --no-install-recommends -y xcvt ``` -2. Copy the GStreamer build tarball from the container image and extract it to `/opt/gstreamer` (change the OS version as needed): +2. Copy the GStreamer build tarball from the container image and extract it to `/opt/gstreamer` (change the OS version as needed, use `sudo` where necessary): ```bash -docker create --name gstreamer ghcr.io/selkies-project/selkies-gstreamer/gstreamer:latest-ubuntu20.04 -docker cp gstreamer:/opt/selkies-gstreamer-latest.tgz /opt/selkies-gstreamer-latest.tgz +docker create --name gstreamer ghcr.io/selkies-project/selkies-gstreamer/gstreamer:master-ubuntu${UBUNTU_RELEASE} +docker cp gstreamer:/opt/selkies-gstreamer /opt/selkies-gstreamer docker rm gstreamer -cd /opt && tar zxvf selkies-gstreamer-latest.tgz ``` This will install the GStreamer components to the default directory of `/opt/gstreamer`. If you are unpacking to a different directory, make sure to set the directory to the environment variable `GSTREAMER_PATH`. -3. Copy the Python Wheel file from the container image and install it: +3. Copy the Python Wheel file from the container image and install it, use `sudo` where necessary: ```bash -docker create --name selkies-py ghcr.io/selkies-project/selkies-gstreamer/py-build:latest -docker cp selkies-py:/opt/pypi/dist/selkies_gstreamer_disla-1.0.0rc0-py3-none-any.whl /opt/selkies_gstreamer_disla-1.0.0rc0-py3-none-any.whl +docker create --name selkies-py ghcr.io/selkies-project/selkies-gstreamer/py-build:master +docker cp selkies-py:/opt/pypi/dist/selkies_gstreamer-0.0.0.dev0-py3-none-any.whl /opt/selkies_gstreamer-0.0.0.dev0-py3-none-any.whl docker rm selkies-py -python3 -m pip install /opt/selkies_gstreamer_disla-1.0.0rc0-py3-none-any.whl -python3 -m pip install --upgrade --force-reinstall https://github.com/python-xlib/python-xlib/archive/e8cf018.zip +pip3 install /opt/selkies_gstreamer-0.0.0.dev0-py3-none-any.whl +pip3 install --upgrade --force-reinstall https://github.com/python-xlib/python-xlib/archive/e8cf018.zip ``` -4. Install the HTML5 components to the container image: +4. Install the HTML5 components to the container image, use `sudo` where necessary: ```bash -docker create --name gst-web ghcr.io/selkies-project/selkies-gstreamer/gst-web:latest +docker create --name gst-web ghcr.io/selkies-project/selkies-gstreamer/gst-web:master cd /opt && docker cp gst-web:/usr/share/nginx/html ./gst-web docker rm gst-web ``` @@ -195,7 +194,7 @@ cd /usr/local/cuda/lib64 && sudo find . -maxdepth 1 -type l -name "*libnvrtc.so. export DISPLAY=:0 export GST_DEBUG=*:2 export PULSE_SERVER=127.0.0.1:4713 -# Initialize the GStreamer environment after setting GSTREAMER_PATH to the path of your gstreamer directory +# Initialize the GStreamer environment after setting GSTREAMER_PATH to the path of your GStreamer directory export GSTREAMER_PATH=/opt/gstreamer source /opt/gstreamer/gst-env # Start a virtual X server, skip this line if an X server already exists or you are already using a display @@ -205,8 +204,8 @@ until [[ -S /tmp/.X11-unix/X0 ]]; do sleep 1; done && echo 'X Server is ready' # Initialize PulseAudio, TCP interface to port 4713 must be configured if using a separate setup sudo /usr/bin/pulseaudio -k >/dev/null 2>&1 sudo /usr/bin/pulseaudio --daemonize --system --verbose --log-target=file:/tmp/pulseaudio.log --realtime=true --disallow-exit -L 'module-native-protocol-tcp auth-ip-acl=127.0.0.0/8 port=4713 auth-anonymous=1' -# Replace this line with your desktop environment session, use VirtualGL `vglrun` here if needed -icewm-session & +# Replace this line with your desktop environment session or skip this line if already running, use VirtualGL `vglrun` here if needed +[[ "${START_XFCE4:-true}" == "true" ]] && rm -rf ~/.config/xfce4 && xfce4-session & # Write Progressive Web App (PWA) config. export PWA_APP_NAME="Selkies WebRTC" export PWA_APP_SHORT_NAME="selkies" @@ -231,17 +230,17 @@ selkies-gstreamer-resize 1280x720 selkies-gstreamer & ``` -### Usage +## Usage -#### Locking the cursor and fullscreen mode +### Locking the cursor and fullscreen mode -The cursor can be locked into the web interface using `Control + Shift + Left Click` in web browsers supporting the Pointer Lock API. This is useful for most games or graphics applications where the cursor must be confined to the remote screen. The fullscreen mode is available with the shortcut `Control + Shift + F`. +The cursor can be locked into the web interface using `Control + Shift + Left Click` in web browsers supporting the Pointer Lock API. Press `Escape` to exit this remote cursor mode. This remote cursor capability is useful for most games or graphics applications where the cursor must be confined to the remote screen. Fullscreen mode is available with the shortcut `Control + Shift + F`, or by pressing the fullscreen button in the configuration menu. Press `Escape` for a long time to exit fullscreen mode. The configuration menu is available by clicking the small button on the right of the interface with fullscreen turned off, or by using the shortcut `Control + Shift + M`. -#### Command-line options and environment variables +### Command-line options and environment variables Use `selkies-gstreamer --help` for all command-line options, after sourcing `gst-env`. Environment variables for each of the command-line options are available within [`__main__.py`](src/selkies_gstreamer/__main__.py). -#### GStreamer components +### GStreamer components Below are GStreamer components which are implemented and therefore may be used with `selkies-gstreamer`. Some include environment variables or command-line options which may be used select one type of component, and others are chosen automatically based on the operating system or configuration. This section is to be continuously updated. @@ -285,7 +284,7 @@ This table specifies the currently supported transport protocol components. |---|---|---|---|---|---| | [`webrtcbin`](https://gstreamer.freedesktop.org/documentation/webrtc/index.html) | [WebRTC](https://webrtc.org) | All | All Major | Various | N/A | -### Using a TURN server +## Using a TURN server **You are at the right place if the HTML5 web interface loads and the signalling connection works, but the WebRTC connection fails and therefore the remote desktop does not start.** @@ -301,13 +300,13 @@ An open-source TURN server for Linux or UNIX-like operating systems that may be For all other major operating systems including Windows, [Pion TURN](https://github.com/pion/turn)'s `turn-server-simple` executable or [eturnal](https://eturnal.net) are recommended alternative TURN server implementations. [STUNner](https://github.com/l7mp/stunner) is a Kubernetes native STUN and TURN deployment if Helm is possible to be used. -#### Install and run coTURN on a standalone machine or cloud instance +### Install and run coTURN on a standalone machine or cloud instance It is possible to install [coTURN](https://github.com/coturn/coturn) on your own server or PC from a package repository, as long as the listing port and the relay ports may be opened. In short, `/etc/turnserver.conf` must have either the lines `use-auth-secret` and `static-auth-secret=(PUT RANDOM 64 BYTE BASE64 KEY HERE)`, or the lines `lt-cred-mech` and `user=yourusername:yourpassword`. It is strongly recommended to set the `min-port=` and `max-port=` parameters which specifies your relay ports between TURN servers (all ports between this range must be open). Add the line `no-udp-relay` if you cannot open the UDP `min-port=` to `max-port=` port ranges, or the line `no-tcp-relay` if you cannot open the TCP `min-port=` to `max-port=` port ranges. -The `cert=` and `pkey=` options, which lead to the certificate and the private key from a legitimate certificate authority such as [ZeroSSL](https://zerossl.com/features/acme/) (Let's Encrypt may have issues depending on the OS) are required for using TURN over TLS/DTLS, but are otherwise optional. +The `cert=` and `pkey=` options, which lead to the certificate and the private key from a legitimate certificate authority such as [ZeroSSL](https://zerossl.com/features/acme/) (Let's Encrypt may have issues depending on the OS), are required for using TURN over TLS/DTLS, but are otherwise optional. -#### Deploy coTURN with Docker +### Deploy coTURN with Docker In order to deploy a coTURN container, use the following command (consult this [example configuration](https://github.com/coturn/coturn/blob/master/examples/etc/turnserver.conf) for more options which may also be used as command-line arguments). You should be able to expose these ports to the internet. Modify the relay ports `-p 49160-49200:49160-49200/udp` and `--min-port=49160 --max-port=49200` as appropriate (at least one relay port is required). Simply using `--network=host` instead of specifying `-p 49160-49200:49160-49200/udp` is also fine if possible. The relay ports and the listening port must all be open to the internet. Add the `--no-udp-relay` behind `-n` if you cannot open the UDP `min-port=` to `max-port=` port ranges, or `--no-tcp-relay` behind `-n` if you cannot open the TCP `min-port=` to `max-port=` port ranges. @@ -327,7 +326,7 @@ If you want to use TURN over TLS/DTLS, you must have a valid hostname, and also More information available in the [coTURN container image](https://hub.docker.com/r/coturn/coturn) or the [coTURN repository](https://github.com/coturn/coturn) website. -#### Deploy coTURN With Kubernetes +### Deploy coTURN With Kubernetes Before you read, [STUNner](https://github.com/l7mp/stunner) is a pretty good method to deploy a TURN or STUN server on Kubernetes if you are able to use Helm. @@ -343,7 +342,7 @@ If you want to use TURN over TLS/DTLS, use [cert-manager](https://cert-manager.i More information is available in the [coTURN container image](https://hub.docker.com/r/coturn/coturn) or the [coTURN repository](https://github.com/coturn/coturn) website. -#### Start `selkies-gstreamer` with the TURN server credentials +### Start `selkies-gstreamer` with the TURN server credentials Provide the TURN server host address (the environment variable `TURN_HOST` or the command-line option `--turn_host`), port (the environment variable `TURN_PORT` or the command-line option `--turn_port`), and the shared secret (`TURN_SHARED_SECRET`/`--turn_shared_secret`) or the legacy long-term authentication username/password (`TURN_USERNAME`/`--turn_username` and `TURN_PASSWORD`/`--turn_password`) in order to take advantage of the TURN relay capabilities and guarantee connection success. @@ -373,10 +372,20 @@ Any [GStreamer](https://gstreamer.freedesktop.org) plugin [documentation page](h ## Troubleshooting +### The HTML5 web interface is slow and laggy. + +It's most likely something with your network. Ensure that the latency to your TURN server from the server and the client is ideally under 50 ms. If the latency is too high, your connection may be too laggy for any remote desktop application. Moreover, please try to use a wired connection over a wireless connection. Also note that a higher framerate will improve performance if you have the sufficient bandwidth. This is because one screen refresh from a 60 fps screen takes 16.67 ms at a time, while one screen refresh from a 15 fps screen inevitably takes 66.67 ms, and therefore inherently causes a visible lag. + +However, it might be that the parameters for the encoders, WebRTC, RTSP, or other [GStreamer](https://gstreamer.freedesktop.org) plugins are not optimized enough. If you find that it is the case, we always welcome contributions. If your changes show noticeably better results in the same conditions, please make a [Pull Request](https://github.com/selkies-project/selkies-gstreamer/pulls), or tell us about the parameters in any channel that we can reach so that we can also test. + ### The HTML5 web interface loads and the signalling connection works, but the WebRTC connection fails and the remote desktop does not start. Please read [Using a TURN server](#using-a-turn-server). +### I want to pass multiple screens within a server to another client using the WebRTC HTML5 web interface. + +You can start a new instance of `selkies-gstreamer` by changing the `DISPLAY` environment variable and setting a different web interface port in a different terminal to pass a different screen simultaneously to your current screen. + ### I want to test a shared secret TURN server by manually generating a TURN credential from a shared secret. This step is required when you want to test your TURN server configured with a shared secret instead of the legacy username/password authentication. @@ -390,6 +399,7 @@ docker-compose run --service-ports test 2. From inside the test container, source `gst-env` and call the `generate_rtc_config` method. ```bash +export GSTREAMER_PATH=/opt/gstreamer source /opt/gstreamer/gst-env export TURN_HOST="Your TURN Host" diff --git a/docker-compose.yaml b/docker-compose.yaml index 20847ece..0cf6048c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -39,7 +39,7 @@ services: PY_BUILD_IMAGE: selkies-gstreamer-py-build:latest WEB_IMAGE: gst-web:latest PYPI_PACKAGE: selkies_gstreamer - PACKAGE_VERSION: 1.0.0.dev0 + PACKAGE_VERSION: 0.0.0.dev0 environment: # Basic authentication ENABLE_BASIC_AUTH: ${ENABLE_BASIC_AUTH} @@ -71,6 +71,7 @@ services: volumes: - type: bind source: ./src/selkies_gstreamer + # Change python3.8 to python3.6 in 18.04 and python3.10 in 22.04 target: /usr/local/lib/python3.8/dist-packages/selkies_gstreamer read_only: true diff --git a/src/selkies_gstreamer/__init__.py b/src/selkies_gstreamer/__init__.py index 703d5a7c..460d7605 100644 --- a/src/selkies_gstreamer/__init__.py +++ b/src/selkies_gstreamer/__init__.py @@ -3,7 +3,7 @@ import time sys.path.append(os.path.dirname(os.path.abspath(__file__))) -# Verify gstreamer installation +# Verify GStreamer installation retry = True while retry: try: diff --git a/src/selkies_gstreamer/__main__.py b/src/selkies_gstreamer/__main__.py index 1447426e..46dc3c00 100644 --- a/src/selkies_gstreamer/__main__.py +++ b/src/selkies_gstreamer/__main__.py @@ -282,7 +282,7 @@ def main(): parser.add_argument('--json_config', default=os.environ.get( 'JSON_CONFIG', '/var/run/appconfig/streaming_args.json'), - help='Path to JSON file containing argument key-value pairs that are overlayed with cli args/env.') + help='Path to the JSON file containing argument key-value pairs that are overlayed with CLI arguments or environment variables.') parser.add_argument('--addr', default=os.environ.get( 'LISTEN_HOST', '0.0.0.0'), @@ -294,15 +294,15 @@ def main(): parser.add_argument('--enable_basic_auth', default=os.environ.get( 'ENABLE_BASIC_AUTH', 'false'), - help='Enable Basic authentication on server. Must set basic_auth_user and basic_auth_password to enforce Basic auth.') + help='Enable Basic authentication on server. Must set basic_auth_password and optionally basic_auth_user to enforce Basic authentication.') parser.add_argument('--basic_auth_user', default=os.environ.get( 'BASIC_AUTH_USER', os.environ.get('USER', '')), - help='Username for basic auth, default is to use the USER env var. Must also set basic_auth_password to enforce Basic auth.') + help='Username for Basic authentication, default is to use the USER environment variable or a blank username if it does not exist. Must also set basic_auth_password to enforce Basic authentication.') parser.add_argument('--basic_auth_password', default=os.environ.get( 'BASIC_AUTH_PASSWORD', ''), - help='Password used when basic_auth_user is set.') + help='Password used when Basic authentication is set.') parser.add_argument('--web_root', default=os.environ.get( 'WEB_ROOT', '/opt/gst-web'), @@ -318,7 +318,7 @@ def main(): parser.add_argument('--coturn_auth_header_name', default=os.environ.get( 'COTURN_AUTH_HEADER_NAME', 'x-auth-user'), - help='header name to pass user to coturn web service') + help='Header name to pass user to coturn web service') parser.add_argument('--rtc_config_json', default=os.environ.get( 'RTC_CONFIG_JSON', '/tmp/rtc.json'), @@ -326,7 +326,7 @@ def main(): parser.add_argument('--turn_shared_secret', default=os.environ.get( 'TURN_SHARED_SECRET', ''), - help='shared TURN secret used to generate HMAC credentials, also requires TURN_HOST and TURN_PORT.') + help='Shared TURN secret used to generate HMAC credentials, also requires TURN_HOST and TURN_PORT.') parser.add_argument('--turn_username', default=os.environ.get( 'TURN_USERNAME', ''), @@ -350,37 +350,37 @@ def main(): parser.add_argument('--turn_tls', default=os.environ.get( 'TURN_TLS', 'false'), - help='enable or disable TURN over TLS (for the TCP protocol) or TURN over DTLS (for the UDP protocol), valid TURN server certificate required.') + help='Enable or disable TURN over TLS (for the TCP protocol) or TURN over DTLS (for the UDP protocol), valid TURN server certificate required.') parser.add_argument('--uinput_mouse_socket', default=os.environ.get('UINPUT_MOUSE_SOCKET', ''), - help='path to uinput mouse socket provided by uinput-device-plugin, if not provided, uinput is used directly.') + help='Path to uinput mouse socket provided by uinput-device-plugin, if not provided, uinput is used directly.') parser.add_argument('--uinput_js_socket', default=os.environ.get('UINPUT_JS_SOCKET', ''), - help='path to uinput joystick socket provided by uinput-device-plugin, if not provided, uinput is used directly.') + help='Path to uinput joystick socket provided by uinput-device-plugin, if not provided, uinput is used directly.') parser.add_argument('--enable_audio', default=os.environ.get('ENABLE_AUDIO', 'true'), - help='enable or disable audio stream') + help='Enable or disable audio stream') parser.add_argument('--enable_clipboard', default=os.environ.get('ENABLE_CLIPBOARD', 'true'), - help='enable or disable the clipboard features, supported values: true, false, in, out') + help='Enable or disable the clipboard features, supported values: true, false, in, out') parser.add_argument('--app_auto_init', default=os.environ.get('APP_AUTO_INIT', 'true'), - help='if true, skips wait for APP_READY_FILE to exist before starting stream.') + help='If true, skips wait for APP_READY_FILE to exist before starting stream.') parser.add_argument('--app_ready_file', default=os.environ.get('APP_READY_FILE', '/var/run/appconfig/appready'), - help='file set by sidecar used to indicate that app is initialized and ready') + help='File set by sidecar used to indicate that app is initialized and ready') parser.add_argument('--framerate', default=os.environ.get('WEBRTC_FRAMERATE', '30'), - help='framerate of streaming pipeline') + help='Framerate of streaming pipeline') parser.add_argument('--video_bitrate', default=os.environ.get('WEBRTC_VIDEO_BITRATE', '2000'), - help='default video bitrate') + help='Default video bitrate') parser.add_argument('--audio_bitrate', default=os.environ.get('WEBRTC_AUDIO_BITRATE', '64000'), - help='default audio bitrate') + help='Default audio bitrate') parser.add_argument('--encoder', - default=os.environ.get('WEBRTC_ENCODER', 'nvh264enc'), - help='gstreamer encoder plugin to use') + default=os.environ.get('WEBRTC_ENCODER', 'x264enc'), + help='GStreamer encoder plugin to use') parser.add_argument('--enable_resize', default=os.environ.get('WEBRTC_ENABLE_RESIZE', 'true'), help='Enable dynamic resizing to match browser size') @@ -389,7 +389,7 @@ def main(): help='Enable passing remote cursors to client') parser.add_argument('--metrics_port', default=os.environ.get('METRICS_PORT', '8000'), - help='port to start metrics server on') + help='Port to start metrics server on') parser.add_argument('--debug', action='store_true', help='Enable debug logging') args = parser.parse_args() diff --git a/src/selkies_gstreamer/gstwebrtc_app.py b/src/selkies_gstreamer/gstwebrtc_app.py index c852a31a..31ecabd9 100644 --- a/src/selkies_gstreamer/gstwebrtc_app.py +++ b/src/selkies_gstreamer/gstwebrtc_app.py @@ -307,7 +307,7 @@ def build_video_pipeline(self): # encoder x264enc = Gst.ElementFactory.make("x264enc", "x264enc") - x264enc.set_property("threads", 4) + x264enc.set_property("sliced-threads", True) x264enc.set_property("bframes", 0) x264enc.set_property("key-int-max", 0) x264enc.set_property("byte-stream", True) @@ -359,7 +359,6 @@ def build_video_pipeline(self): if self.encoder == "vp9enc": vpenc = Gst.ElementFactory.make("vp9enc", "vpenc") - vpenc.set_property("threads", 4) vpenc_caps = Gst.caps_from_string("video/x-vp9") vpenc_capsfilter = Gst.ElementFactory.make("capsfilter") vpenc_capsfilter.set_property("caps", vpenc_caps) @@ -374,7 +373,6 @@ def build_video_pipeline(self): # VPX Parameters # Borrowed from: https://github.com/nurdism/neko/blob/df98368137732b8aaf840e27cdf2bd41067b2161/server/internal/gst/gst.go#L94 - vpenc.set_property("threads", 2) vpenc.set_property("cpu-used", 8) vpenc.set_property("deadline", 1) vpenc.set_property("error-resilient", "partitions") @@ -696,7 +694,7 @@ def set_framerate(self, framerate): logger.info("framerate set to: %d" % framerate) def set_video_bitrate(self, bitrate): - """Set NvEnc encoder target bitrate in bps + """Set video encoder target bitrate in bps Arguments: bitrate {integer} -- bitrate in bits per second, for example, 2000 for 2kbits/s or 10000 for 1mbit/sec. @@ -948,7 +946,7 @@ def __send_ice(self, webrtcbin, mlineindex, candidate): loop.run_until_complete(self.on_ice(mlineindex, candidate)) def start_pipeline(self): - """Starts the gstreamer pipeline + """Starts the GStreamer pipeline """ logger.info("starting pipeline") diff --git a/src/selkies_gstreamer/signalling_web.py b/src/selkies_gstreamer/signalling_web.py index 97351d7d..3469e316 100644 --- a/src/selkies_gstreamer/signalling_web.py +++ b/src/selkies_gstreamer/signalling_web.py @@ -140,13 +140,13 @@ def __init__(self, loop, options): # Validate TURN args if self.turn_shared_secret: - if not self.turn_host and self.turn_port: - raise Exception("missing turn_host and turn_port options with turn_shared_secret") + if not (self.turn_host and self.turn_port): + raise Exception("missing turn_host or turn_port options with turn_shared_secret") # Validate basic auth args if self.enable_basic_auth: - if not (self.basic_auth_user and self.basic_auth_password): - raise Exception("missing basic_auth_password when using basic_auth_user option.") + if not self.basic_auth_password: + raise Exception("missing basic_auth_password when using enable_basic_auth option.") ############### Helper functions ############### @@ -513,7 +513,7 @@ def main(): parser.add_argument('--health', default='/health', help='Health check route') parser.add_argument('--restart-on-cert-change', default=False, dest='cert_restart', action='store_true', help='Automatically restart if the SSL certificate changes') parser.add_argument('--enable_basic_auth', default="false", help="Use basic auth, must also set basic_auth_user, and basic_auth_password args") - parser.add_argument('--basic_auth_user', default="", help='Username for basic auth, if not set, no authorization will be enforced.') + parser.add_argument('--basic_auth_user', default="", help='Username for basic auth.') parser.add_argument('--basic_auth_password', default="", help='Password for basic auth, if not set, no authorization will be enforced.') options = parser.parse_args(sys.argv[1:]) diff --git a/src/selkies_gstreamer/webrtc_input.py b/src/selkies_gstreamer/webrtc_input.py index 00affbea..92a7aef3 100644 --- a/src/selkies_gstreamer/webrtc_input.py +++ b/src/selkies_gstreamer/webrtc_input.py @@ -202,7 +202,7 @@ def __js_emit(self, *args, **kwargs): async def connect(self): """Connects to X server - The target X server is determiend by the DISPLAY environment variable. + The target X server is determined by the DISPLAY environment variable. """ self.xdisplay = display.Display() @@ -304,6 +304,11 @@ def send_x11_keypress(self, keysym, down=True): down {bool} -- toggle key down or up (default: {True}) """ + # With the Generic 105-key PC layout (default in Linux without a real keyboard), the key '<' is redirected to keycode 94 + # Because keycode 94 with Shift pressed is instead the key '>', the keysym for '<' should instead be redirected to ',' + # Although prevented in most cases, this fix may present issues in some keyboard layouts + if keysym == 60 and self.keyboard._display.keysym_to_keycode(keysym) == 94: + keysym = 44 keycode = pynput.keyboard.KeyCode(keysym) if down: self.keyboard.press(keycode)