mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Compare commits
39 Commits
Author | SHA1 | Date | |
---|---|---|---|
c1360d3abc | |||
950095d6d8 | |||
009bc20969 | |||
395db0fd54 | |||
13fa86d543 | |||
8308c13382 | |||
ec175909a3 | |||
d2f51fa10f | |||
70325e0277 | |||
bdff0841ec | |||
3c17dbe282 | |||
887413d536 | |||
f9228e653d | |||
df634be1c5 | |||
2130daa02d | |||
aee7650d47 | |||
98ba32c574 | |||
d08d3eccef | |||
0dd9597519 | |||
334fcef407 | |||
e868ad4061 | |||
b41d0bf956 | |||
b62fa6ab8b | |||
8dba9cff44 | |||
217cc451ea | |||
1f81bd3efc | |||
50e5483661 | |||
d2765c30fd | |||
76fc892823 | |||
ea99ce7753 | |||
646eaced29 | |||
9104953ad9 | |||
c40243635f | |||
8059002475 | |||
9daf83cc52 | |||
0cebe465a2 | |||
d9403d9c14 | |||
8604a30744 | |||
957e893cf6 |
@ -79,7 +79,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -88,17 +90,13 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
adduser $USERNAME pulse; \
|
||||||
#
|
#
|
||||||
# setup pulseaudio
|
|
||||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
|
||||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
|
||||||
#
|
|
||||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# make directories for neko
|
||||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
mkdir -p /etc/neko /var/www /var/log/neko /home/$USERNAME/.config/pulse /home/$USERNAME/.local/share/xorg; \
|
||||||
chmod 1777 /var/log/neko; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
@ -118,6 +116,7 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
ENV DISPLAY=:99.0
|
||||||
|
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||||
ENV NEKO_PASSWORD=neko
|
ENV NEKO_PASSWORD=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
@ -85,7 +85,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -94,17 +96,13 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
adduser $USERNAME pulse; \
|
||||||
#
|
#
|
||||||
# setup pulseaudio
|
|
||||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
|
||||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
|
||||||
#
|
|
||||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# make directories for neko
|
||||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
mkdir -p /etc/neko /var/www /var/log/neko /home/$USERNAME/.config/pulse /home/$USERNAME/.local/share/xorg; \
|
||||||
chmod 1777 /var/log/neko; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
@ -124,6 +122,7 @@ COPY .docker/base/xorg.conf /etc/neko/xorg.conf
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
ENV DISPLAY=:99.0
|
||||||
|
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||||
ENV NEKO_PASSWORD=neko
|
ENV NEKO_PASSWORD=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
@ -88,7 +88,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -97,17 +99,13 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
adduser $USERNAME pulse; \
|
||||||
#
|
#
|
||||||
# setup pulseaudio
|
|
||||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
|
||||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
|
||||||
#
|
|
||||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# make directories for neko
|
||||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
mkdir -p /etc/neko /var/www /var/log/neko /home/$USERNAME/.config/pulse /home/$USERNAME/.local/share/xorg; \
|
||||||
chmod 1777 /var/log/neko; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
@ -128,9 +126,11 @@ COPY .docker/base/intel/add-render-group.sh /usr/bin/add-render-group.sh
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
ENV DISPLAY=:99.0
|
||||||
|
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||||
ENV NEKO_PASSWORD=neko
|
ENV NEKO_PASSWORD=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
ENV NEKO_HWENC=VAAPI
|
||||||
ENV RENDER_GID=
|
ENV RENDER_GID=
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -1,10 +1,62 @@
|
|||||||
ARG UBUNTU_RELEASE=20.04
|
ARG UBUNTU_RELEASE=20.04
|
||||||
ARG CUDA_VERSION=11.2.2
|
ARG CUDA_VERSION=11.4.3
|
||||||
|
ARG VIRTUALGL_VERSION=3.1
|
||||||
|
ARG GSTREAMER_VERSION=1.20
|
||||||
|
|
||||||
|
#
|
||||||
|
# STAGE 0: Build gstreamer with nvidia plugins.
|
||||||
|
#
|
||||||
|
FROM ubuntu:${UBUNTU_RELEASE} AS gstreamer
|
||||||
|
ARG GSTREAMER_VERSION
|
||||||
|
|
||||||
|
#
|
||||||
|
# install dependencies
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
RUN set -eux; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
# Install essentials
|
||||||
|
curl build-essential ca-certificates git \
|
||||||
|
# Install pip and ninja
|
||||||
|
python3-pip python-gi-dev ninja-build \
|
||||||
|
# Install build deps
|
||||||
|
autopoint autoconf automake autotools-dev libtool gettext bison flex gtk-doc-tools \
|
||||||
|
# Install libraries
|
||||||
|
librtmp-dev \
|
||||||
|
libvo-aacenc-dev \
|
||||||
|
libtool-bin \
|
||||||
|
libgtk2.0-dev \
|
||||||
|
libgl1-mesa-dev \
|
||||||
|
libopus-dev \
|
||||||
|
libpulse-dev \
|
||||||
|
libssl-dev \
|
||||||
|
libx264-dev \
|
||||||
|
libvpx-dev; \
|
||||||
|
# Install meson
|
||||||
|
pip3 install meson; \
|
||||||
|
#
|
||||||
|
# clean up
|
||||||
|
apt-get clean -y; \
|
||||||
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
|
#
|
||||||
|
# build gstreamer
|
||||||
|
RUN set -eux; \
|
||||||
|
git clone --depth 1 --branch $GSTREAMER_VERSION https://gitlab.freedesktop.org/gstreamer/gstreamer.git /gstreamer/src; \
|
||||||
|
cd /gstreamer/src; \
|
||||||
|
mkdir -p /opt/gstreamer; \
|
||||||
|
meson --prefix /opt/gstreamer \
|
||||||
|
-Dgpl=enabled \
|
||||||
|
-Dugly=enabled \
|
||||||
|
-Dgst-plugins-ugly:x264=enabled \
|
||||||
|
build; \
|
||||||
|
ninja -C build; \
|
||||||
|
meson install -C build;
|
||||||
|
|
||||||
#
|
#
|
||||||
# STAGE 1: SERVER
|
# STAGE 1: SERVER
|
||||||
#
|
#
|
||||||
FROM golang:1.18-bullseye as server
|
FROM golang:1.20-bullseye as server
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -35,7 +87,7 @@ RUN go get -v -t -d . && go build -o bin/neko cmd/neko/main.go
|
|||||||
#
|
#
|
||||||
# STAGE 2: CLIENT
|
# STAGE 2: CLIENT
|
||||||
#
|
#
|
||||||
FROM node:14-bullseye-slim as client
|
FROM node:18-bullseye-slim as client
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
#
|
#
|
||||||
@ -51,13 +103,13 @@ RUN npm run build
|
|||||||
#
|
#
|
||||||
# STAGE 3: RUNTIME
|
# STAGE 3: RUNTIME
|
||||||
#
|
#
|
||||||
FROM nvcr.io/nvidia/cudagl:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
|
FROM nvidia/cuda:${CUDA_VERSION}-runtime-ubuntu${UBUNTU_RELEASE} as runtime
|
||||||
|
|
||||||
ARG UBUNTU_RELEASE
|
ARG UBUNTU_RELEASE
|
||||||
ARG CUDA_VERSION
|
ARG VIRTUALGL_VERSION
|
||||||
|
|
||||||
# Make all NVIDIA GPUs visible by default
|
# Make all NVIDIA GPUs visible by default
|
||||||
ENV NVIDIA_VISIBLE_DEVICES=all
|
ENV NVIDIA_VISIBLE_DEVICES all
|
||||||
# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
|
# All NVIDIA driver capabilities should preferably be used, check `NVIDIA_DRIVER_CAPABILITIES` inside the container if things do not work
|
||||||
ENV NVIDIA_DRIVER_CAPABILITIES all
|
ENV NVIDIA_DRIVER_CAPABILITIES all
|
||||||
|
|
||||||
@ -75,20 +127,55 @@ ARG USERNAME=neko
|
|||||||
ARG USER_UID=1000
|
ARG USER_UID=1000
|
||||||
ARG USER_GID=$USER_UID
|
ARG USER_GID=$USER_UID
|
||||||
|
|
||||||
|
RUN set -eux; \
|
||||||
|
dpkg --add-architecture i386; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
# opengl base: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/base/Dockerfile
|
||||||
|
libxau6 libxau6:i386 \
|
||||||
|
libxdmcp6 libxdmcp6:i386 \
|
||||||
|
libxcb1 libxcb1:i386 \
|
||||||
|
libxext6 libxext6:i386 \
|
||||||
|
libx11-6 libx11-6:i386 \
|
||||||
|
# opengl runtime: https://gitlab.com/nvidia/container-images/opengl/-/blob/ubuntu20.04/glvnd/runtime/Dockerfile
|
||||||
|
libglvnd0 libglvnd0:i386 \
|
||||||
|
libgl1 libgl1:i386 \
|
||||||
|
libglx0 libglx0:i386 \
|
||||||
|
libegl1 libegl1:i386 \
|
||||||
|
libgles2 libgles2:i386 \
|
||||||
|
# hardware accleration utilities
|
||||||
|
libglu1 libglu1:i386 \
|
||||||
|
libvulkan-dev libvulkan-dev:i386 \
|
||||||
|
mesa-utils mesa-utils-extra \
|
||||||
|
mesa-va-drivers mesa-vulkan-drivers \
|
||||||
|
vainfo vdpauinfo; \
|
||||||
|
#
|
||||||
|
# install vulkan-utils or vulkan-tools depending on ubuntu release
|
||||||
|
if [ "${UBUNTU_RELEASE}" = "18.04" ]; then \
|
||||||
|
apt-get install -y --no-install-recommends vulkan-utils; \
|
||||||
|
else \
|
||||||
|
apt-get install -y --no-install-recommends vulkan-tools; \
|
||||||
|
fi; \
|
||||||
|
#
|
||||||
|
# create symlink for libnvrtc.so (needed for cudaconvert)
|
||||||
|
find /usr/local/cuda/lib64/ -maxdepth 1 -type l -name "*libnvrtc.so.*" -exec sh -c 'ln -sf {} /usr/local/cuda/lib64/libnvrtc.so' \;; \
|
||||||
|
#
|
||||||
|
# clean up
|
||||||
|
apt-get clean -y; \
|
||||||
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
|
#
|
||||||
|
# add cuda to ld path, for gstreamer cuda plugins
|
||||||
|
ENV LD_LIBRARY_PATH="/usr/lib/x86_64-linux-gnu:/usr/lib/i386-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}:/usr/local/cuda/lib:/usr/local/cuda/lib64"
|
||||||
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
#
|
#
|
||||||
# install dependencies
|
# install dependencies
|
||||||
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
|
apt-get install -y --no-install-recommends wget ca-certificates supervisor; \
|
||||||
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
|
apt-get install -y --no-install-recommends pulseaudio dbus-x11 xserver-xorg-video-dummy; \
|
||||||
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6; \
|
apt-get install -y --no-install-recommends libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx6 libx264-155 libvo-aacenc0 librtmp1; \
|
||||||
#
|
apt-get install -y --no-install-recommends libgtk-3-bin software-properties-common cabextract aptitude vim curl; \
|
||||||
# hardware acclerations utilities
|
|
||||||
apt-get install -y --no-install-recommends libgtk-3-bin mesa-utils mesa-utils-extra mesa-va-drivers mesa-vulkan-drivers libvulkan-dev libvulkan-dev:i386 vdpauinfo; \
|
|
||||||
#
|
|
||||||
# gst
|
|
||||||
apt-get install -y --no-install-recommends libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev \
|
|
||||||
gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-pulseaudio; \
|
|
||||||
#
|
#
|
||||||
# install fonts
|
# install fonts
|
||||||
apt-get install -y --no-install-recommends \
|
apt-get install -y --no-install-recommends \
|
||||||
@ -97,7 +184,9 @@ RUN set -eux; \
|
|||||||
# Japanese fonts
|
# Japanese fonts
|
||||||
fonts-takao-mincho \
|
fonts-takao-mincho \
|
||||||
# Chinese fonts
|
# Chinese fonts
|
||||||
fonts-wqy-zenhei; \
|
fonts-wqy-zenhei xfonts-intl-chinese xfonts-wqy \
|
||||||
|
# Korean fonts
|
||||||
|
fonts-wqy-microhei; \
|
||||||
#
|
#
|
||||||
# create a non-root user
|
# create a non-root user
|
||||||
groupadd --gid $USER_GID $USERNAME; \
|
groupadd --gid $USER_GID $USERNAME; \
|
||||||
@ -106,17 +195,13 @@ RUN set -eux; \
|
|||||||
adduser $USERNAME video; \
|
adduser $USERNAME video; \
|
||||||
adduser $USERNAME pulse; \
|
adduser $USERNAME pulse; \
|
||||||
#
|
#
|
||||||
# setup pulseaudio
|
|
||||||
mkdir -p /home/$USERNAME/.config/pulse/; \
|
|
||||||
echo "default-server=unix:/tmp/pulseaudio.socket" > /home/$USERNAME/.config/pulse/client.conf; \
|
|
||||||
#
|
|
||||||
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
# workaround for an X11 problem: http://blog.tigerteufel.de/?p=476
|
||||||
mkdir /tmp/.X11-unix; \
|
mkdir /tmp/.X11-unix; \
|
||||||
chmod 1777 /tmp/.X11-unix; \
|
chmod 1777 /tmp/.X11-unix; \
|
||||||
chown $USERNAME /tmp/.X11-unix/; \
|
chown $USERNAME /tmp/.X11-unix/; \
|
||||||
#
|
#
|
||||||
# make directories for neko
|
# make directories for neko
|
||||||
mkdir -p /etc/neko /var/www /var/log/neko; \
|
mkdir -p /etc/neko /var/www /var/log/neko /home/$USERNAME/.config/pulse /home/$USERNAME/.local/share/xorg; \
|
||||||
chmod 1777 /var/log/neko; \
|
chmod 1777 /var/log/neko; \
|
||||||
chown $USERNAME /var/log/neko/; \
|
chown $USERNAME /var/log/neko/; \
|
||||||
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
chown -R $USERNAME:$USERNAME /home/$USERNAME; \
|
||||||
@ -126,18 +211,18 @@ RUN set -eux; \
|
|||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
#
|
#
|
||||||
# install and configure Vulkan manually
|
# configure EGL and Vulkan manually
|
||||||
RUN set -eux; \
|
RUN VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)') && \
|
||||||
apt-get update; \
|
# Configure EGL manually
|
||||||
if [ "${UBUNTU_RELEASE}" = "18.04" ]; then apt-get install -y --no-install-recommends vulkan-utils; else apt-get install -y --no-install-recommends vulkan-tools; fi; \
|
mkdir -p /usr/share/glvnd/egl_vendor.d/ && \
|
||||||
#
|
echo "{\n\
|
||||||
# clean up
|
\"file_format_version\" : \"1.0.0\",\n\
|
||||||
apt-get clean -y; \
|
\"ICD\": {\n\
|
||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*; \
|
\"library_path\": \"libEGL_nvidia.so.0\"\n\
|
||||||
#
|
}\n\
|
||||||
# configure vulkan
|
}" > /usr/share/glvnd/egl_vendor.d/10_nvidia.json && \
|
||||||
VULKAN_API_VERSION=$(dpkg -s libvulkan1 | grep -oP 'Version: [0-9|\.]+' | grep -oP '[0-9]+(\.[0-9]+)(\.[0-9]+)'); \
|
# Configure Vulkan manually
|
||||||
mkdir -p /etc/vulkan/icd.d/; \
|
mkdir -p /etc/vulkan/icd.d/ && \
|
||||||
echo "{\n\
|
echo "{\n\
|
||||||
\"file_format_version\" : \"1.0.0\",\n\
|
\"file_format_version\" : \"1.0.0\",\n\
|
||||||
\"ICD\": {\n\
|
\"ICD\": {\n\
|
||||||
@ -148,7 +233,6 @@ RUN set -eux; \
|
|||||||
|
|
||||||
#
|
#
|
||||||
# install VirtualGL and make libraries available for preload
|
# install VirtualGL and make libraries available for preload
|
||||||
ARG VIRTUALGL_VERSION=3.1
|
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \
|
wget "https://sourceforge.net/projects/virtualgl/files/virtualgl_${VIRTUALGL_VERSION}_amd64.deb"; \
|
||||||
@ -178,10 +262,21 @@ COPY .docker/base/nvidia/entrypoint.sh /bin/entrypoint.sh
|
|||||||
# set default envs
|
# set default envs
|
||||||
ENV USER=$USERNAME
|
ENV USER=$USERNAME
|
||||||
ENV DISPLAY=:99.0
|
ENV DISPLAY=:99.0
|
||||||
|
ENV PULSE_SERVER=unix:/tmp/pulseaudio.socket
|
||||||
ENV NEKO_PASSWORD=neko
|
ENV NEKO_PASSWORD=neko
|
||||||
ENV NEKO_PASSWORD_ADMIN=admin
|
ENV NEKO_PASSWORD_ADMIN=admin
|
||||||
ENV NEKO_BIND=:8080
|
ENV NEKO_BIND=:8080
|
||||||
|
|
||||||
|
#
|
||||||
|
# set gstreamer envs
|
||||||
|
ENV PATH="/opt/gstreamer/bin:${PATH}"
|
||||||
|
ENV LD_LIBRARY_PATH="/opt/gstreamer/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
|
||||||
|
ENV PKG_CONFIG_PATH="/opt/gstreamer/lib/x86_64-linux-gnu/pkgconfig${PKG_CONFIG_PATH:+:${PKG_CONFIG_PATH}}"
|
||||||
|
|
||||||
|
#
|
||||||
|
# copy gstreamer from previous stage
|
||||||
|
COPY --from=gstreamer /opt/gstreamer /opt/gstreamer
|
||||||
|
|
||||||
#
|
#
|
||||||
# copy static files from previous stages
|
# copy static files from previous stages
|
||||||
COPY --from=server /src/bin/neko /usr/bin/neko
|
COPY --from=server /src/bin/neko /usr/bin/neko
|
||||||
|
@ -1,5 +1,8 @@
|
|||||||
#!/usr/bin/pulseaudio -nF
|
#!/usr/bin/pulseaudio -nF
|
||||||
|
|
||||||
|
### Create virtual output device sink
|
||||||
|
load-module module-null-sink sink_name=audio_output sink_properties=device.description="Virtual\ Audio\ Output"
|
||||||
|
|
||||||
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
|
# Allow pulse audio to be accessed via TCP (from localhost only), to allow other users to access the virtual devices
|
||||||
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1
|
load-module module-native-protocol-unix socket=/tmp/pulseaudio.socket auth-anonymous=1
|
||||||
|
|
||||||
|
@ -4,9 +4,13 @@ FROM $BASE_IMAGE
|
|||||||
#
|
#
|
||||||
# install neko chromium
|
# install neko chromium
|
||||||
RUN set -eux; \
|
RUN set -eux; \
|
||||||
echo "deb http://ftp.de.debian.org/debian bookworm main" >> /etc/apt/sources.list; \
|
|
||||||
apt-get update; \
|
apt-get update; \
|
||||||
apt-get install -y --no-install-recommends unzip chromium chromium-common chromium-sandbox openbox; \
|
apt-get install -y --no-install-recommends software-properties-common; \
|
||||||
|
# chromium-browser from default repo needs snap to be installed
|
||||||
|
# and nvidia base is ubuntu not debian
|
||||||
|
add-apt-repository ppa:system76/pop; \
|
||||||
|
apt-get update; \
|
||||||
|
apt-get install -y --no-install-recommends unzip chromium openbox; \
|
||||||
#
|
#
|
||||||
# install widevine module
|
# install widevine module
|
||||||
CHROMIUM_DIR="/usr/lib/chromium"; \
|
CHROMIUM_DIR="/usr/lib/chromium"; \
|
||||||
|
@ -15,6 +15,11 @@ RUN set -eux; apt-get update; \
|
|||||||
apt-get clean -y; \
|
apt-get clean -y; \
|
||||||
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
rm -rf /var/lib/apt/lists/* /var/cache/apt/*
|
||||||
|
|
||||||
|
#
|
||||||
|
# disable autolock
|
||||||
|
RUN kwriteconfig5 --file /home/neko/.config/kscreenlockerrc --group Daemon --key Autolock false; \
|
||||||
|
chown neko:neko /home/neko/.config/kscreenlockerrc
|
||||||
|
|
||||||
#
|
#
|
||||||
# copy configuation files
|
# copy configuation files
|
||||||
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
COPY supervisord.conf /etc/neko/supervisord/kde.conf
|
||||||
|
@ -17,5 +17,9 @@ fi
|
|||||||
|
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
-v "${PWD}/../server:/src" \
|
-v "${PWD}/../server:/src" \
|
||||||
--entrypoint="go" \
|
-e GIT_COMMIT=`git rev-parse --short HEAD` \
|
||||||
neko_dev_server build -o "bin/neko" "cmd/neko/main.go"
|
-e GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD` \
|
||||||
|
-e GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"` \
|
||||||
|
--entrypoint="bash" \
|
||||||
|
--workdir="/src" \
|
||||||
|
neko_dev_server ./build
|
||||||
|
@ -16,7 +16,7 @@ if [ ! -d "${PWD}/../client/node_modules" ] || [ "$1" == "-i" ]; then
|
|||||||
-v "${PWD}/../client:/app" \
|
-v "${PWD}/../client:/app" \
|
||||||
--workdir="/app" \
|
--workdir="/app" \
|
||||||
--entrypoint="npm" \
|
--entrypoint="npm" \
|
||||||
node:14-buster-slim install
|
node:18-bullseye-slim install
|
||||||
fi
|
fi
|
||||||
|
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
@ -25,5 +25,5 @@ docker run --rm -it \
|
|||||||
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
-e "VUE_APP_SERVER_PORT=${SERVER_PORT}" \
|
||||||
--workdir="/app" \
|
--workdir="/app" \
|
||||||
--entrypoint="npm" \
|
--entrypoint="npm" \
|
||||||
node:14-buster-slim run serve
|
node:18-bullseye-slim run serve
|
||||||
|
|
@ -17,9 +17,22 @@ if [ ! -f "${BINARY_PATH}" ] || [ "$1" == "-r" ]; then
|
|||||||
./rebuild-server
|
./rebuild-server
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# if image starts with nvidia- add --gpus all
|
||||||
|
if [[ "${SERVER_TAG}" == "nvidia-"* ]]; then
|
||||||
|
GPU_FLAG="--gpus all"
|
||||||
|
echo "Nvidia GPU acceleration enabled"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# if image starts with intel- add --device /dev/dri
|
||||||
|
if [[ "${SERVER_TAG}" == "intel-"* ]]; then
|
||||||
|
GPU_FLAG="--device /dev/dri"
|
||||||
|
echo "Intel GPU acceleration enabled"
|
||||||
|
fi
|
||||||
|
|
||||||
# use --gpus all to enable GPU acceleration
|
# use --gpus all to enable GPU acceleration
|
||||||
docker run --rm -it \
|
docker run --rm -it \
|
||||||
--name "neko_dev" \
|
--name "neko_dev" \
|
||||||
|
$GPU_FLAG \
|
||||||
-p "${SERVER_PORT}:8080" \
|
-p "${SERVER_PORT}:8080" \
|
||||||
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
-p "${SERVER_EPR}:${SERVER_EPR}/udp" \
|
||||||
-e "NEKO_SCREEN=1920x1080@60" \
|
-e "NEKO_SCREEN=1920x1080@60" \
|
||||||
|
@ -1,17 +1,22 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="neko" :class="[side ? 'expanded' : '']">
|
<div id="neko" :class="[!videoOnly && side ? 'expanded' : '']">
|
||||||
<template v-if="!$client.supported">
|
<template v-if="!$client.supported">
|
||||||
<neko-unsupported />
|
<neko-unsupported />
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<main class="neko-main">
|
<main class="neko-main">
|
||||||
<div v-if="!hideControls" class="header-container">
|
<div v-if="!videoOnly" class="header-container">
|
||||||
<neko-header />
|
<neko-header />
|
||||||
</div>
|
</div>
|
||||||
<div class="video-container">
|
<div class="video-container">
|
||||||
<neko-video ref="video" :hideControls="hideControls" @control-attempt="controlAttempt" />
|
<neko-video
|
||||||
|
ref="video"
|
||||||
|
:hideControls="hideControls"
|
||||||
|
:extraControls="isEmbedMode"
|
||||||
|
@control-attempt="controlAttempt"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="!hideControls" class="room-container">
|
<div v-if="!videoOnly" class="room-container">
|
||||||
<neko-members />
|
<neko-members />
|
||||||
<div class="room-menu">
|
<div class="room-menu">
|
||||||
<div class="settings">
|
<div class="settings">
|
||||||
@ -26,11 +31,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<neko-side v-if="!hideControls && side" />
|
<neko-side v-if="!videoOnly && side" />
|
||||||
<neko-connect v-if="!connected" />
|
<neko-connect v-if="!connected" />
|
||||||
<neko-about v-if="about" />
|
<neko-about v-if="about" />
|
||||||
<notifications
|
<notifications
|
||||||
v-if="!hideControls"
|
v-if="!videoOnly"
|
||||||
group="neko"
|
group="neko"
|
||||||
position="top left"
|
position="top left"
|
||||||
style="top: 50px; pointer-events: none"
|
style="top: 50px; pointer-events: none"
|
||||||
@ -176,10 +181,32 @@
|
|||||||
|
|
||||||
shakeKbd = false
|
shakeKbd = false
|
||||||
|
|
||||||
get hideControls() {
|
get volume() {
|
||||||
|
const numberParam = parseFloat(new URL(location.href).searchParams.get('volume') || '1.0')
|
||||||
|
return Math.max(0.0, Math.min(!isNaN(numberParam) ? numberParam * 100 : 100, 100))
|
||||||
|
}
|
||||||
|
|
||||||
|
get isCastMode() {
|
||||||
return !!new URL(location.href).searchParams.get('cast')
|
return !!new URL(location.href).searchParams.get('cast')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isEmbedMode() {
|
||||||
|
return !!new URL(location.href).searchParams.get('embed')
|
||||||
|
}
|
||||||
|
|
||||||
|
get hideControls() {
|
||||||
|
return this.isCastMode
|
||||||
|
}
|
||||||
|
|
||||||
|
get videoOnly() {
|
||||||
|
return this.isCastMode || this.isEmbedMode
|
||||||
|
}
|
||||||
|
|
||||||
|
@Watch('volume', { immediate: true })
|
||||||
|
onVolume(volume: number) {
|
||||||
|
this.$accessor.video.setVolume(volume)
|
||||||
|
}
|
||||||
|
|
||||||
@Watch('hideControls', { immediate: true })
|
@Watch('hideControls', { immediate: true })
|
||||||
onHideControls(enabled: boolean) {
|
onHideControls(enabled: boolean) {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
@ -21,26 +21,29 @@
|
|||||||
@mouseup.stop.prevent="onMouseUp"
|
@mouseup.stop.prevent="onMouseUp"
|
||||||
@mouseenter.stop.prevent="onMouseEnter"
|
@mouseenter.stop.prevent="onMouseEnter"
|
||||||
@mouseleave.stop.prevent="onMouseLeave"
|
@mouseleave.stop.prevent="onMouseLeave"
|
||||||
|
@touchmove.stop.prevent="onTouchHandler"
|
||||||
|
@touchstart.stop.prevent="onTouchHandler"
|
||||||
|
@touchend.stop.prevent="onTouchHandler"
|
||||||
/>
|
/>
|
||||||
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="toggle">
|
<div v-if="!playing && playable" class="player-overlay" @click.stop.prevent="playAndUnmute">
|
||||||
<i class="fas fa-play-circle" />
|
<i class="fas fa-play-circle" />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
<div v-else-if="mutedOverlay && muted" class="player-overlay" @click.stop.prevent="unmute">
|
||||||
<i class="fas fa-volume-up" />
|
<i class="fas fa-volume-up" />
|
||||||
</div>
|
</div>
|
||||||
<div ref="aspect" class="player-aspect" />
|
<div ref="aspect" class="player-aspect" />
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="!fullscreen" class="video-menu top">
|
<ul v-if="!fullscreen && !hideControls" class="video-menu top">
|
||||||
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
|
||||||
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
<li v-if="admin"><i @click.stop.prevent="openResolution" class="fas fa-desktop"></i></li>
|
||||||
<li :class="hideControls || 'request-control'">
|
<li v-if="!implicitHosting" :class="extraControls || 'extra-control'">
|
||||||
<i
|
<i
|
||||||
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
:class="[hosted && !hosting ? 'disabled' : '', !hosted && !hosting ? 'faded' : '', 'fas', 'fa-keyboard']"
|
||||||
@click.stop.prevent="toggleControl"
|
@click.stop.prevent="toggleControl"
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<ul v-if="!fullscreen" class="video-menu bottom">
|
<ul v-if="!fullscreen && !hideControls" class="video-menu bottom">
|
||||||
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
|
<li v-if="hosting && (!clipboard_read_available || !clipboard_write_available)">
|
||||||
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
<i @click.stop.prevent="openClipboard" class="fas fa-clipboard"></i>
|
||||||
</li>
|
</li>
|
||||||
@ -106,12 +109,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.request-control {
|
/* usually extra controls are only shown on mobile */
|
||||||
|
&.extra-control {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
&.request-control {
|
&.extra-control {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -223,13 +226,15 @@
|
|||||||
@Ref('resolution') readonly _resolution!: Resolution
|
@Ref('resolution') readonly _resolution!: Resolution
|
||||||
@Ref('clipboard') readonly _clipboard!: Clipboard
|
@Ref('clipboard') readonly _clipboard!: Clipboard
|
||||||
|
|
||||||
|
// all controls are hidden (e.g. for cast mode)
|
||||||
@Prop(Boolean) readonly hideControls!: boolean
|
@Prop(Boolean) readonly hideControls!: boolean
|
||||||
|
// extra controls are shown (e.g. for embed mode)
|
||||||
|
@Prop(Boolean) readonly extraControls!: boolean
|
||||||
|
|
||||||
private keyboard = GuacamoleKeyboard()
|
private keyboard = GuacamoleKeyboard()
|
||||||
private observer = new ResizeObserver(this.onResize.bind(this))
|
private observer = new ResizeObserver(this.onResize.bind(this))
|
||||||
private focused = false
|
private focused = false
|
||||||
private fullscreen = false
|
private fullscreen = false
|
||||||
private startsMuted = true
|
|
||||||
private mutedOverlay = true
|
private mutedOverlay = true
|
||||||
|
|
||||||
get admin() {
|
get admin() {
|
||||||
@ -361,7 +366,6 @@
|
|||||||
onMutedChanged(muted: boolean) {
|
onMutedChanged(muted: boolean) {
|
||||||
if (this._video && this._video.muted != muted) {
|
if (this._video && this._video.muted != muted) {
|
||||||
this._video.muted = muted
|
this._video.muted = muted
|
||||||
this.startsMuted = muted
|
|
||||||
|
|
||||||
if (!muted) {
|
if (!muted) {
|
||||||
this.mutedOverlay = false
|
this.mutedOverlay = false
|
||||||
@ -384,9 +388,29 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Watch('playing')
|
@Watch('playing')
|
||||||
onPlayingChanged(playing: boolean) {
|
async onPlayingChanged(playing: boolean) {
|
||||||
if (this._video && this._video.paused && playing) {
|
if (this._video && this._video.paused && playing) {
|
||||||
this.play()
|
// if autoplay is disabled, play() will throw an error
|
||||||
|
// and we need to properly save the state otherwise we
|
||||||
|
// would be thinking we're playing when we're not
|
||||||
|
try {
|
||||||
|
await this._video.play()
|
||||||
|
} catch (err: any) {
|
||||||
|
if (!this._video.muted) {
|
||||||
|
// video.play() can fail if audio is set due restrictive
|
||||||
|
// browsers autoplay policy -> retry with muted audio
|
||||||
|
try {
|
||||||
|
this.$accessor.video.setMuted(true)
|
||||||
|
this._video.muted = true
|
||||||
|
await this._video.play()
|
||||||
|
} catch (err: any) {
|
||||||
|
// if it still fails, we're not playing anything
|
||||||
|
this.$accessor.video.pause()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$accessor.video.pause()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._video && !this._video.paused && !playing) {
|
if (this._video && !this._video.paused && !playing) {
|
||||||
@ -424,11 +448,6 @@
|
|||||||
this._video.addEventListener('canplaythrough', () => {
|
this._video.addEventListener('canplaythrough', () => {
|
||||||
this.$accessor.video.setPlayable(true)
|
this.$accessor.video.setPlayable(true)
|
||||||
if (this.autoplay) {
|
if (this.autoplay) {
|
||||||
// start as muted due to restrictive browsers autoplay policy
|
|
||||||
if (this.startsMuted && (!document.hasFocus() || !this.$accessor.active)) {
|
|
||||||
this.$accessor.video.setMuted(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.$accessor.video.play()
|
this.$accessor.video.play()
|
||||||
})
|
})
|
||||||
@ -560,6 +579,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
playAndUnmute() {
|
||||||
|
this.$accessor.video.play()
|
||||||
|
this.$accessor.video.setMuted(false)
|
||||||
|
}
|
||||||
|
|
||||||
unmute() {
|
unmute() {
|
||||||
this.$accessor.video.setMuted(false)
|
this.$accessor.video.setMuted(false)
|
||||||
}
|
}
|
||||||
@ -667,6 +691,35 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onTouchHandler(e: TouchEvent) {
|
||||||
|
let first = e.changedTouches[0]
|
||||||
|
let type = ''
|
||||||
|
switch (e.type) {
|
||||||
|
case 'touchstart':
|
||||||
|
type = 'mousedown'
|
||||||
|
break
|
||||||
|
case 'touchmove':
|
||||||
|
type = 'mousemove'
|
||||||
|
break
|
||||||
|
case 'touchend':
|
||||||
|
type = 'mouseup'
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const simulatedEvent = new MouseEvent(type, {
|
||||||
|
bubbles: true,
|
||||||
|
cancelable: true,
|
||||||
|
view: window,
|
||||||
|
screenX: first.screenX,
|
||||||
|
screenY: first.screenY,
|
||||||
|
clientX: first.clientX,
|
||||||
|
clientY: first.clientY,
|
||||||
|
})
|
||||||
|
first.target.dispatchEvent(simulatedEvent)
|
||||||
|
}
|
||||||
|
|
||||||
onMouseDown(e: MouseEvent) {
|
onMouseDown(e: MouseEvent) {
|
||||||
if (!this.hosting) {
|
if (!this.hosting) {
|
||||||
this.$emit('control-attempt', e)
|
this.$emit('control-attempt', e)
|
||||||
|
@ -203,11 +203,12 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer = new RTCPeerConnection()
|
|
||||||
if (lite !== true) {
|
if (lite !== true) {
|
||||||
this._peer = new RTCPeerConnection({
|
this._peer = new RTCPeerConnection({
|
||||||
iceServers: servers,
|
iceServers: servers,
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
|
this._peer = new RTCPeerConnection()
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.onconnectionstatechange = () => {
|
this._peer.onconnectionstatechange = () => {
|
||||||
@ -251,11 +252,28 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
|
|
||||||
this._peer.ontrack = this.onTrack.bind(this)
|
this._peer.ontrack = this.onTrack.bind(this)
|
||||||
|
|
||||||
|
this._peer.onicecandidate = (event: RTCPeerConnectionIceEvent) => {
|
||||||
|
if (!event.candidate) {
|
||||||
|
this.emit('debug', `sent all local ICE candidates`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = event.candidate.toJSON()
|
||||||
|
this.emit('debug', `sending local ICE candidate`, init)
|
||||||
|
|
||||||
|
this._ws!.send(
|
||||||
|
JSON.stringify({
|
||||||
|
event: EVENT.SIGNAL.CANDIDATE,
|
||||||
|
data: JSON.stringify(init),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
this._peer.onnegotiationneeded = async () => {
|
this._peer.onnegotiationneeded = async () => {
|
||||||
this.emit('warn', `negotiation is needed`)
|
this.emit('warn', `negotiation is needed`)
|
||||||
|
|
||||||
const d = await this._peer!.createOffer()
|
const d = await this._peer!.createOffer()
|
||||||
this._peer!.setLocalDescription(d)
|
await this._peer!.setLocalDescription(d)
|
||||||
|
|
||||||
this._ws!.send(
|
this._ws!.send(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@ -277,10 +295,10 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.setRemoteDescription({ type: 'offer', sdp })
|
await this._peer.setRemoteDescription({ type: 'offer', sdp })
|
||||||
|
|
||||||
for (const candidate of this._candidates) {
|
for (const candidate of this._candidates) {
|
||||||
this._peer.addIceCandidate(candidate)
|
await this._peer.addIceCandidate(candidate)
|
||||||
}
|
}
|
||||||
this._candidates = []
|
this._candidates = []
|
||||||
|
|
||||||
@ -310,7 +328,7 @@ export abstract class BaseClient extends EventEmitter<BaseEvents> {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this._peer.setRemoteDescription({ type: 'answer', sdp })
|
await this._peer.setRemoteDescription({ type: 'answer', sdp })
|
||||||
}
|
}
|
||||||
|
|
||||||
private async onMessage(e: MessageEvent) {
|
private async onMessage(e: MessageEvent) {
|
||||||
|
@ -2,20 +2,34 @@
|
|||||||
|
|
||||||
## master branch
|
## master branch
|
||||||
|
|
||||||
|
## [n.eko v2.8.0](https://github.com/m1k1o/neko/releases/tag/v2.8.0)
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
- Added AV1 tag, metadata and pipeline. Unfortunately does not work yet, since the encoding is way too slow (by @mbattista).
|
||||||
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
|
- Added `m1k1o/neko:kde` tag as an alternative to `m1k1o/neko:xfce`.
|
||||||
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
|
- New VirtualGL version 3.1 was released, adding support for Chromium browsers to use Nvidia GPU acceleration!
|
||||||
|
- Added `?embed=1` parameter to the URL, which will hide the sidebar and the top bar, so that it can be embedded in other websites.
|
||||||
|
- Added `?volume=<0-1>` parameter to the URL, which will set the inital volume of the player (by @urbanekpj).
|
||||||
|
- Touch events are now supported on mobile devices (by @urbanekpj).
|
||||||
|
- Added NVENC support, hardware h264 encoding for Nvidia GPUs!
|
||||||
|
- Fixed an issue where `nvh264enc` did not send SPS and PPS NAL units (by @mbattista).
|
||||||
|
|
||||||
### Bugs
|
### Bugs
|
||||||
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
- Fixed TCP mux occasional freeze by adding write buffer to it.
|
||||||
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
|
- Fixed stereo problem in chromium-based browsers, where it was only as mono by adding `stereo=1` to opus SDP to clients answer.
|
||||||
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
|
- Fixed keysym mapping for unknown keycodes, which was causing some key combinations to not work on some keyboards.
|
||||||
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
|
- Fixed a bug where `max_fps=0` would lead to an invalid pipeline.
|
||||||
|
- Fixed client side webrtc ICE gathering, so that neko can be used without exposed ports, only with STUN and TURN servers.
|
||||||
|
- Fixed play state synchronization, when autoplay is disabled.
|
||||||
|
|
||||||
### Misc
|
### Misc
|
||||||
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
- Updated to go 1.19 and Node 18, removed go-events as dependency (by @mbattista).
|
||||||
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
|
- Added adaptive framerate which now streams in the framerate you selected from the dropdown.
|
||||||
|
- Improved chinese and korean characters support.
|
||||||
|
- Disabled autolock for kde, so that it does not lock the screen when you are not using it.
|
||||||
|
- Refactored autoplay, so that it will start playing audio, if it's allowed by the browser (by @urbanekpj).
|
||||||
|
- Renamed pulseaudio sink from `auto_null` to `audio_output`, because it was ignored by KDE.
|
||||||
|
- Pulseaudio is now configured using environment variables, so that users can mount `/home/neko` without losing audio configuration.
|
||||||
|
|
||||||
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)
|
## [n.eko v2.7](https://github.com/m1k1o/neko/releases/tag/v2.7)
|
||||||
|
|
||||||
|
@ -162,6 +162,63 @@ services:
|
|||||||
"RestoreOnStartup": 1,
|
"RestoreOnStartup": 1,
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Nvidia GPU acceleration
|
||||||
|
|
||||||
|
You need to have [nvidia-docker](https://github.com/NVIDIA/nvidia-docker) installed, start the container with `--gpus all` flag and use images built for nvidia (see above).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -d --gpus all \
|
||||||
|
-p 8080:8080 \
|
||||||
|
-p 56000-56100:56000-56100/udp \
|
||||||
|
-e NEKO_SCREEN=1920x1080@30 \
|
||||||
|
-e NEKO_PASSWORD=neko \
|
||||||
|
-e NEKO_PASSWORD_ADMIN=admin \
|
||||||
|
-e NEKO_EPR=56000-56100 \
|
||||||
|
-e NEKO_NAT1TO1=192.168.1.10 \
|
||||||
|
-e NEKO_ICELITE=1 \
|
||||||
|
-e NEKO_VIDEO_CODEC=h264 \
|
||||||
|
-e NEKO_HWENC=nvenc \
|
||||||
|
--shm-size=2gb \
|
||||||
|
--cap-add=SYS_ADMIN \
|
||||||
|
--name neko \
|
||||||
|
ghcr.io/m1k1o/neko/nvidia-google-chrome:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to use docker-compose, you can use this example:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: "3.4"
|
||||||
|
services:
|
||||||
|
neko:
|
||||||
|
image: "ghcr.io/m1k1o/neko/nvidia-google-chrome:latest"
|
||||||
|
restart: "unless-stopped"
|
||||||
|
shm_size: "2gb"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
- "56000-56100:56000-56100/udp"
|
||||||
|
cap_add:
|
||||||
|
- SYS_ADMIN
|
||||||
|
environment:
|
||||||
|
NEKO_SCREEN: '1920x1080@30'
|
||||||
|
NEKO_PASSWORD: neko
|
||||||
|
NEKO_PASSWORD_ADMIN: admin
|
||||||
|
NEKO_EPR: 56000-56100
|
||||||
|
NEKO_NAT1TO1: 192.168.1.10
|
||||||
|
NEKO_VIDEO_CODEC: h264
|
||||||
|
NEKO_HWENC: nvenc
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities: [gpu]
|
||||||
|
```
|
||||||
|
|
||||||
|
- You can verify that GPU is available inside the container by running `docker exec -it neko nvidia-smi` command.
|
||||||
|
- You can verify that GPU is used for encoding by searching for `nvh264enc` in `docker logs neko` output.
|
||||||
|
- If you don'ŧ specify `NEKO_HWENC: nvenc` environment variable, CPU encoding will be used but GPU will still be available for browser rendering.
|
||||||
|
|
||||||
### Want to use VPN for your n.eko browsing?
|
### Want to use VPN for your n.eko browsing?
|
||||||
- Check this out: https://github.com/m1k1o/neko-vpn
|
- Check this out: https://github.com/m1k1o/neko-vpn
|
||||||
|
|
||||||
@ -179,6 +236,8 @@ services:
|
|||||||
- Adding `?pwd=<password>` will prefill password.
|
- Adding `?pwd=<password>` will prefill password.
|
||||||
- Adding `?usr=<display-name>` will prefill username.
|
- Adding `?usr=<display-name>` will prefill username.
|
||||||
- Adding `?cast=1` will hide all control and show only video.
|
- Adding `?cast=1` will hide all control and show only video.
|
||||||
|
- Adding `?embed=1` will hide most additional components and show only video.
|
||||||
|
- Adding `?volume=<0-1>` will set volume to given value.
|
||||||
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
- e.g. `http(s)://<URL:Port>/?pwd=neko&usr=guest&cast=1`
|
||||||
|
|
||||||
### Screen size
|
### Screen size
|
||||||
|
@ -25,7 +25,7 @@ nat1to1: <ip>
|
|||||||
- Control protection means, users can gain control only if at least one admin is in the room.
|
- Control protection means, users can gain control only if at least one admin is in the room.
|
||||||
- e.g. `false`
|
- e.g. `false`
|
||||||
#### `NEKO_IMPLICIT_CONTROL`:
|
#### `NEKO_IMPLICIT_CONTROL`:
|
||||||
- If enabled members can gain control implicitly, they don't needd to request control.
|
- If enabled members can gain control implicitly, they don't need to request control.
|
||||||
- e.g. `false`
|
- e.g. `false`
|
||||||
#### `NEKO_LOCKS`:
|
#### `NEKO_LOCKS`:
|
||||||
- Resources, that will be locked when starting, separated by whitespace.
|
- Resources, that will be locked when starting, separated by whitespace.
|
||||||
@ -79,13 +79,14 @@ nat1to1: <ip>
|
|||||||
- `gstreamer1.0-plugins-good`
|
- `gstreamer1.0-plugins-good`
|
||||||
- `gstreamer1.0-plugins-bad`
|
- `gstreamer1.0-plugins-bad`
|
||||||
- `gstreamer1.0-plugins-ugly`
|
- `gstreamer1.0-plugins-ugly`
|
||||||
- e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream`
|
- e.g. `ximagesrc display-name=%s show-pointer=true use-damage=false ! video/x-raw,framerate=30/1 ! videoconvert ! queue ! video/x-raw,format=NV12 ! x264enc threads=4 bitrate=3500 key-int-max=60 vbv-buf-capacity=4000 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline`
|
||||||
#### `NEKO_MAX_FPS`:
|
#### `NEKO_MAX_FPS`:
|
||||||
- The resulting stream frames per seconds should be capped *(0 for uncapped)*.
|
- The resulting stream frames per seconds should be capped *(0 for uncapped)*.
|
||||||
- e.g. `0`
|
- e.g. `0`
|
||||||
#### `NEKO_HWENC`:
|
#### `NEKO_HWENC`:
|
||||||
- Use hardware accelerated encoding, for now supported only `VAAPI`.
|
- none *(default CPU encoding)*
|
||||||
- e.g. `VAAPI`
|
- vaapi
|
||||||
|
- nvenc
|
||||||
|
|
||||||
### Audio
|
### Audio
|
||||||
|
|
||||||
@ -167,7 +168,7 @@ Flags:
|
|||||||
--cert string path to the SSL cert used to secure the neko server
|
--cert string path to the SSL cert used to secure the neko server
|
||||||
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
--control_protection control protection means, users can gain control only if at least one admin is in the room
|
||||||
--cors strings list of allowed origins for CORS (default [*])
|
--cors strings list of allowed origins for CORS (default [*])
|
||||||
--device string audio device to capture (default "auto_null.monitor")
|
--device string audio device to capture (default "audio_output.monitor")
|
||||||
--display string XDisplay to capture (default ":99.0")
|
--display string XDisplay to capture (default ":99.0")
|
||||||
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
--epr string limits the pool of ephemeral ports that ICE UDP connections can allocate from (default "59000-59100")
|
||||||
--file_transfer_enabled enable file transfer feature (default false)
|
--file_transfer_enabled enable file transfer feature (default false)
|
||||||
|
@ -95,9 +95,9 @@ services:
|
|||||||
! videoconvert
|
! videoconvert
|
||||||
! queue
|
! queue
|
||||||
! video/x-raw,framerate=30/1,format=NV12
|
! video/x-raw,framerate=30/1,format=NV12
|
||||||
! v4l2h264enc extra-controls="controls,h264_profile=0,video_bitrate=1250000;"
|
! v4l2h264enc extra-controls="controls,h264_profile=1,video_bitrate=1250000;"
|
||||||
! h264parse config-interval=3
|
! h264parse config-interval=3
|
||||||
! video/x-h264,profile=baseline,stream-format=byte-stream
|
! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
|
||||||
NEKO_VIDEO_CODEC: h264
|
NEKO_VIDEO_CODEC: h264
|
||||||
```
|
```
|
||||||
|
|
||||||
|
22
server/build
22
server/build
@ -3,8 +3,22 @@
|
|||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
BUILD_TIME=`date -u +'%Y-%m-%dT%H:%M:%SZ'`
|
||||||
GIT_COMMIT=`git rev-parse --short HEAD`
|
|
||||||
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
|
||||||
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
|
|
||||||
|
|
||||||
go build -o bin/neko -ldflags "-s -X 'm1k1o/neko.buildDate=${BUILD_TIME}' -X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}' -X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'" -i cmd/neko/main.go
|
#
|
||||||
|
# set git build variables if git exists
|
||||||
|
if git status > /dev/null 2>&1 && [ -z $GIT_COMMIT ] && [ -z $GIT_BRANCH ] && [ -z $GIT_DIRTY ];
|
||||||
|
then
|
||||||
|
GIT_COMMIT=`git rev-parse --short HEAD`
|
||||||
|
GIT_BRANCH=`git rev-parse --symbolic-full-name --abbrev-ref HEAD`
|
||||||
|
GIT_DIRTY=`git diff-index --quiet HEAD -- || echo "✗-"`
|
||||||
|
fi
|
||||||
|
|
||||||
|
go build \
|
||||||
|
-o bin/neko \
|
||||||
|
-ldflags "
|
||||||
|
-s -w
|
||||||
|
-X 'm1k1o/neko.buildDate=${BUILD_TIME}'
|
||||||
|
-X 'm1k1o/neko.gitCommit=${GIT_DIRTY}${GIT_COMMIT}'
|
||||||
|
-X 'm1k1o/neko.gitBranch=${GIT_BRANCH}'
|
||||||
|
" \
|
||||||
|
cmd/neko/main.go;
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"m1k1o/neko/internal/capture/gst"
|
"m1k1o/neko/internal/capture/gst"
|
||||||
|
"m1k1o/neko/internal/config"
|
||||||
"m1k1o/neko/internal/types/codec"
|
"m1k1o/neko/internal/types/codec"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ func NewBroadcastPipeline(device string, display string, pipelineSrc string, url
|
|||||||
return pipelineStr, nil
|
return pipelineStr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc string) (string, error) {
|
func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc string, fps int16, bitrate uint, hwenc config.HwEnc) (string, error) {
|
||||||
pipelineStr := " ! appsink name=appsinkvideo"
|
pipelineStr := " ! appsink name=appsinkvideo"
|
||||||
|
|
||||||
// if using custom pipeline
|
// if using custom pipeline
|
||||||
@ -68,7 +69,7 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
|||||||
|
|
||||||
switch rtpCodec.Name {
|
switch rtpCodec.Name {
|
||||||
case codec.VP8().Name:
|
case codec.VP8().Name:
|
||||||
if hwenc == "VAAPI" {
|
if hwenc == config.HwEncVAAPI {
|
||||||
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
if err := gst.CheckPlugins([]string{"ximagesrc", "vaapi"}); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -138,35 +139,40 @@ func NewVideoPipeline(rtpCodec codec.RTPCodec, display string, pipelineSrc strin
|
|||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if hwenc == "VAAPI" {
|
vbvbuf := uint(1000)
|
||||||
|
if bitrate > 1000 {
|
||||||
|
vbvbuf = bitrate
|
||||||
|
}
|
||||||
|
|
||||||
|
if hwenc == config.HwEncVAAPI {
|
||||||
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
|
if err := gst.CheckPlugins([]string{"vaapi"}); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate)
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! vaapih264enc rate-control=vbr bitrate=%d keyframe-period=180 quality-level=7 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate)
|
||||||
|
} else if hwenc == config.HwEncNVENC {
|
||||||
|
if err := gst.CheckPlugins([]string{"nvcodec"}); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! nvh264enc name=encoder preset=2 gop-size=25 spatial-aq=true temporal-aq=true bitrate=%d vbv-buffer-size=%d rc-mode=6 ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||||
} else {
|
} else {
|
||||||
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
// https://gstreamer.freedesktop.org/documentation/openh264/openh264enc.html?gi-language=c#openh264enc
|
||||||
// gstreamer1.0-plugins-bad
|
// gstreamer1.0-plugins-bad
|
||||||
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
// openh264enc multi-thread=4 complexity=high bitrate=3072000 max-bitrate=4096000
|
||||||
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
|
if err := gst.CheckPlugins([]string{"openh264"}); err == nil {
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
|
pipelineStr = fmt.Sprintf(videoSrc+"openh264enc multi-thread=4 complexity=high bitrate=%d max-bitrate=%d ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate*1000, (bitrate+1024)*1000)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
// https://gstreamer.freedesktop.org/documentation/x264/index.html?gi-language=c
|
||||||
// gstreamer1.0-plugins-ugly
|
// gstreamer1.0-plugins-ugly
|
||||||
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream
|
// video/x-raw,format=I420 ! x264enc bframes=0 key-int-max=60 byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline
|
||||||
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
|
if err := gst.CheckPlugins([]string{"x264"}); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
vbvbuf := uint(1000)
|
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream,profile=constrained-baseline"+pipelineStr, display, fps, bitrate, vbvbuf)
|
||||||
if bitrate > 1000 {
|
|
||||||
vbvbuf = bitrate
|
|
||||||
}
|
|
||||||
|
|
||||||
pipelineStr = fmt.Sprintf(videoSrc+"video/x-raw,format=NV12 ! x264enc threads=4 bitrate=%d key-int-max=60 vbv-buf-capacity=%d byte-stream=true tune=zerolatency speed-preset=veryfast ! video/x-h264,stream-format=byte-stream"+pipelineStr, display, fps, bitrate, vbvbuf)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
return "", fmt.Errorf("unknown codec %s", rtpCodec.Name)
|
||||||
|
@ -2,6 +2,7 @@ package config
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"m1k1o/neko/internal/types/codec"
|
"m1k1o/neko/internal/types/codec"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
@ -9,13 +10,21 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type HwEnc int
|
||||||
|
|
||||||
|
const (
|
||||||
|
HwEncNone HwEnc = iota
|
||||||
|
HwEncVAAPI
|
||||||
|
HwEncNVENC
|
||||||
|
)
|
||||||
|
|
||||||
type Capture struct {
|
type Capture struct {
|
||||||
// video
|
// video
|
||||||
Display string
|
Display string
|
||||||
VideoCodec codec.RTPCodec
|
VideoCodec codec.RTPCodec
|
||||||
VideoHWEnc string // TODO: Pipeline builder.
|
VideoHWEnc HwEnc // TODO: Pipeline builder.
|
||||||
VideoBitrate uint // TODO: Pipeline builder.
|
VideoBitrate uint // TODO: Pipeline builder.
|
||||||
VideoMaxFPS int16 // TODO: Pipeline builder.
|
VideoMaxFPS int16 // TODO: Pipeline builder.
|
||||||
VideoPipeline string
|
VideoPipeline string
|
||||||
|
|
||||||
// audio
|
// audio
|
||||||
@ -92,7 +101,7 @@ func (Capture) Init(cmd *cobra.Command) error {
|
|||||||
// audio
|
// audio
|
||||||
//
|
//
|
||||||
|
|
||||||
cmd.PersistentFlags().String("device", "auto_null.monitor", "audio device to capture")
|
cmd.PersistentFlags().String("device", "audio_output.monitor", "audio device to capture")
|
||||||
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
if err := viper.BindPFlag("device", cmd.PersistentFlags().Lookup("device")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -184,11 +193,19 @@ func (s *Capture) Set() {
|
|||||||
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
log.Warn().Msg("you are using deprecated config setting 'NEKO_AV1=true', use 'NEKO_VIDEO_CODEC=av1' instead")
|
||||||
}
|
}
|
||||||
|
|
||||||
videoHWEnc := ""
|
videoHWEnc := strings.ToLower(viper.GetString("hwenc"))
|
||||||
if viper.GetString("hwenc") == "VAAPI" {
|
switch videoHWEnc {
|
||||||
videoHWEnc = "VAAPI"
|
case "":
|
||||||
|
fallthrough
|
||||||
|
case "none":
|
||||||
|
s.VideoHWEnc = HwEncNone
|
||||||
|
case "vaapi":
|
||||||
|
s.VideoHWEnc = HwEncVAAPI
|
||||||
|
case "nvenc":
|
||||||
|
s.VideoHWEnc = HwEncNVENC
|
||||||
|
default:
|
||||||
|
log.Warn().Str("hwenc", videoHWEnc).Msgf("unknown video hw encoder, using CPU")
|
||||||
}
|
}
|
||||||
s.VideoHWEnc = videoHWEnc
|
|
||||||
|
|
||||||
s.VideoBitrate = viper.GetUint("video_bitrate")
|
s.VideoBitrate = viper.GetUint("video_bitrate")
|
||||||
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
s.VideoMaxFPS = int16(viper.GetInt("max_fps"))
|
||||||
|
@ -131,6 +131,17 @@ func (session *Session) SignalLocalAnswer(sdp string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (session *Session) SignalLocalCandidate(data string) error {
|
||||||
|
if session.socket == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
session.logger.Info().Msg("signal update - LocalCandidate")
|
||||||
|
return session.socket.Send(&message.SignalCandidate{
|
||||||
|
Event: event.SIGNAL_CANDIDATE,
|
||||||
|
Data: data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (session *Session) SignalRemoteOffer(sdp string) error {
|
func (session *Session) SignalRemoteOffer(sdp string) error {
|
||||||
if session.peer == nil {
|
if session.peer == nil {
|
||||||
return nil
|
return nil
|
||||||
@ -154,14 +165,12 @@ func (session *Session) SignalRemoteAnswer(sdp string) error {
|
|||||||
return session.peer.SetAnswer(sdp)
|
return session.peer.SetAnswer(sdp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) SignalCandidate(data string) error {
|
func (session *Session) SignalRemoteCandidate(data string) error {
|
||||||
if session.socket == nil {
|
if session.socket == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return session.socket.Send(&message.SignalCandidate{
|
session.logger.Info().Msg("signal update - RemoteCandidate")
|
||||||
Event: event.SIGNAL_CANDIDATE,
|
return session.peer.SetCandidate(data)
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session *Session) destroy() error {
|
func (session *Session) destroy() error {
|
||||||
|
@ -40,9 +40,10 @@ type Session interface {
|
|||||||
Send(v interface{}) error
|
Send(v interface{}) error
|
||||||
SignalLocalOffer(sdp string) error
|
SignalLocalOffer(sdp string) error
|
||||||
SignalLocalAnswer(sdp string) error
|
SignalLocalAnswer(sdp string) error
|
||||||
|
SignalLocalCandidate(data string) error
|
||||||
SignalRemoteOffer(sdp string) error
|
SignalRemoteOffer(sdp string) error
|
||||||
SignalRemoteAnswer(sdp string) error
|
SignalRemoteAnswer(sdp string) error
|
||||||
SignalCandidate(data string) error
|
SignalRemoteCandidate(data string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type SessionManager interface {
|
type SessionManager interface {
|
||||||
|
@ -21,6 +21,7 @@ type Peer interface {
|
|||||||
CreateAnswer() (string, error)
|
CreateAnswer() (string, error)
|
||||||
SetOffer(sdp string) error
|
SetOffer(sdp string) error
|
||||||
SetAnswer(sdp string) error
|
SetAnswer(sdp string) error
|
||||||
|
SetCandidate(candidateString string) error
|
||||||
WriteData(v interface{}) error
|
WriteData(v interface{}) error
|
||||||
Destroy() error
|
Destroy() error
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package webrtc
|
package webrtc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/pion/webrtc/v3"
|
"github.com/pion/webrtc/v3"
|
||||||
@ -49,6 +50,16 @@ func (peer *Peer) SetAnswer(sdp string) error {
|
|||||||
return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
|
return peer.connection.SetRemoteDescription(webrtc.SessionDescription{SDP: sdp, Type: webrtc.SDPTypeAnswer})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (peer *Peer) SetCandidate(candidateString string) error {
|
||||||
|
var candidate webrtc.ICECandidateInit
|
||||||
|
err := json.Unmarshal([]byte(candidateString), &candidate)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return peer.connection.AddICECandidate(candidate)
|
||||||
|
}
|
||||||
|
|
||||||
func (peer *Peer) WriteData(v interface{}) error {
|
func (peer *Peer) WriteData(v interface{}) error {
|
||||||
peer.mu.Lock()
|
peer.mu.Lock()
|
||||||
defer peer.mu.Unlock()
|
defer peer.mu.Unlock()
|
||||||
|
@ -123,7 +123,6 @@ func (manager *WebRTCManager) initAPI() error {
|
|||||||
LoggerFactory: logger,
|
LoggerFactory: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
|
||||||
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
settings.SetNAT1To1IPs(manager.config.NAT1To1IPs, webrtc.ICECandidateTypeHost)
|
||||||
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
settings.SetICETimeouts(6*time.Second, 6*time.Second, 3*time.Second)
|
||||||
settings.SetSRTPReplayProtectionWindow(512)
|
settings.SetSRTPReplayProtectionWindow(512)
|
||||||
@ -168,12 +167,15 @@ func (manager *WebRTCManager) initAPI() error {
|
|||||||
|
|
||||||
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
networkType = append(networkType, webrtc.NetworkTypeUDP4)
|
||||||
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
|
manager.logger.Info().Int("port", manager.config.UDPMUX).Msg("using UDP MUX")
|
||||||
|
} else if manager.config.EphemeralMax != 0 {
|
||||||
|
_ = settings.SetEphemeralUDPPortRange(manager.config.EphemeralMin, manager.config.EphemeralMax)
|
||||||
|
networkType = append(networkType,
|
||||||
|
webrtc.NetworkTypeUDP4,
|
||||||
|
webrtc.NetworkTypeUDP6,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enable support for TCP and UDP ICE candidates
|
settings.SetNetworkTypes(networkType)
|
||||||
if len(networkType) > 0 {
|
|
||||||
settings.SetNetworkTypes(networkType)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create MediaEngine with selected codecs
|
// Create MediaEngine with selected codecs
|
||||||
engine := webrtc.MediaEngine{}
|
engine := webrtc.MediaEngine{}
|
||||||
@ -299,7 +301,7 @@ func (manager *WebRTCManager) CreatePeer(id string, session types.Session) (type
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.SignalCandidate(string(candidateString)); err != nil {
|
if err := session.SignalLocalCandidate(string(candidateString)); err != nil {
|
||||||
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
|
manager.logger.Warn().Err(err).Msg("sending SignalCandidate failed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,6 +87,12 @@ func (h *MessageHandler) Message(id string, raw []byte) error {
|
|||||||
utils.Unmarshal(payload, raw, func() error {
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
return h.signalRemoteAnswer(id, session, payload)
|
return h.signalRemoteAnswer(id, session, payload)
|
||||||
}), "%s failed", header.Event)
|
}), "%s failed", header.Event)
|
||||||
|
case event.SIGNAL_CANDIDATE:
|
||||||
|
payload := &message.SignalCandidate{}
|
||||||
|
return errors.Wrapf(
|
||||||
|
utils.Unmarshal(payload, raw, func() error {
|
||||||
|
return h.signalRemoteCandidate(id, session, payload)
|
||||||
|
}), "%s failed", header.Event)
|
||||||
|
|
||||||
// Control Events
|
// Control Events
|
||||||
case event.CONTROL_RELEASE:
|
case event.CONTROL_RELEASE:
|
||||||
|
@ -45,3 +45,7 @@ func (h *MessageHandler) signalRemoteAnswer(id string, session types.Session, pa
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *MessageHandler) signalRemoteCandidate(id string, session types.Session, payload *message.SignalCandidate) error {
|
||||||
|
return session.SignalRemoteCandidate(payload.Data)
|
||||||
|
}
|
||||||
|
@ -39,7 +39,7 @@ var (
|
|||||||
// Major version when you make incompatible API changes,
|
// Major version when you make incompatible API changes,
|
||||||
major = "2"
|
major = "2"
|
||||||
// Minor version when you add functionality in a backwards-compatible manner, and
|
// Minor version when you add functionality in a backwards-compatible manner, and
|
||||||
minor = "7"
|
minor = "8"
|
||||||
// Patch version when you make backwards-compatible bug fixes.
|
// Patch version when you make backwards-compatible bug fixes.
|
||||||
patch = "0"
|
patch = "0"
|
||||||
)
|
)
|
||||||
|
Reference in New Issue
Block a user