mirror of
https://github.com/datarhei/restreamer.git
synced 2025-12-12 06:24:08 +01:00
ADD v0.1.0-rc.7
This commit is contained in:
10
CHANGELOG.md
10
CHANGELOG.md
@@ -1,3 +1,13 @@
|
|||||||
|
## Changes from 0.1.0-RC6.1 to 0.1.0-rc.7
|
||||||
|
|
||||||
|
* security improvements
|
||||||
|
* FFmpeg and NGINX optimizations
|
||||||
|
* fixed update check
|
||||||
|
* added semantic versioning
|
||||||
|
* several small bugfixes and improvements
|
||||||
|
* updated dependencies
|
||||||
|
* added Aarch64 Docker image and reduced Docker layers
|
||||||
|
|
||||||
## Changes from 0.1.0-RC6 to 0.1.0-RC6.1
|
## Changes from 0.1.0-RC6 to 0.1.0-RC6.1
|
||||||
|
|
||||||
* fixed external streaming with RTSP over TCP input option
|
* fixed external streaming with RTSP over TCP input option
|
||||||
|
|||||||
64
Dockerfile
64
Dockerfile
@@ -1,25 +1,25 @@
|
|||||||
FROM node:5.7.1-slim
|
FROM node:5.9.0-slim
|
||||||
|
|
||||||
MAINTAINER datarhei <info@datarhei.org>
|
MAINTAINER datarhei <info@datarhei.org>
|
||||||
|
|
||||||
ENV FFMPEG_VERSION 2.8.6
|
ENV FFMPEG_VERSION=2.8.6 \
|
||||||
ENV YASM_VERSION 1.3.0
|
YASM_VERSION=1.3.0 \
|
||||||
ENV LAME_VERSION 3_99_5
|
LAME_VERSION=3_99_5 \
|
||||||
ENV NGINX_VERSION 1.9.9
|
NGINX_VERSION=1.9.9 \
|
||||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
NGINX_RTMP_VERSION=1.1.7.10 \
|
||||||
|
|
||||||
ENV SRC "/usr/local"
|
SRC="/usr/local" \
|
||||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
LD_LIBRARY_PATH="${SRC}/lib" \
|
||||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
PKG_CONFIG_PATH="${SRC}/lib/pkgconfig" \
|
||||||
|
|
||||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
BUILDDEPS="autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
||||||
|
|
||||||
RUN rm -rf /var/lib/apt/lists/* && \
|
RUN rm -rf /var/lib/apt/lists/* && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS} && \
|
||||||
|
|
||||||
# yasm
|
# yasm
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
cd "yasm-${YASM_VERSION}" && \
|
cd "yasm-${YASM_VERSION}" && \
|
||||||
@@ -29,10 +29,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# x264
|
# x264
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||||
cd x264 && \
|
cd x264 && \
|
||||||
./configure \
|
./configure \
|
||||||
@@ -43,10 +43,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# libmp3lame
|
# libmp3lame
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
cd "lame-RELEASE__${LAME_VERSION}" && \
|
cd "lame-RELEASE__${LAME_VERSION}" && \
|
||||||
@@ -58,11 +58,11 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# ffmpeg
|
# ffmpeg
|
||||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||||
@@ -92,12 +92,12 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
cd tools && \
|
cd tools && \
|
||||||
make qt-faststart && \
|
make qt-faststart && \
|
||||||
cp qt-faststart "${SRC}/bin" && \
|
cp qt-faststart "${SRC}/bin" && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf" && \
|
||||||
RUN ffmpeg -buildconf
|
ffmpeg -buildconf && \
|
||||||
|
|
||||||
# nginx-rtmp
|
# nginx-rtmp
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
||||||
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
||||||
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
||||||
@@ -108,9 +108,9 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
COPY . /restreamer
|
COPY . /restreamer
|
||||||
@@ -123,8 +123,8 @@ RUN npm install -g bower grunt grunt-cli nodemon public-ip eslint && \
|
|||||||
npm cache clean && \
|
npm cache clean && \
|
||||||
bower cache clean --allow-root
|
bower cache clean --allow-root
|
||||||
|
|
||||||
ENV RS_USERNAME admin
|
ENV RS_USERNAME admin \
|
||||||
ENV RS_PASSWORD datarhei
|
RS_PASSWORD datarhei
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
VOLUME ["/restreamer/db"]
|
VOLUME ["/restreamer/db"]
|
||||||
|
|||||||
149
Dockerfile_aarch64
Normal file
149
Dockerfile_aarch64
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
FROM aarch64/debian:jessie
|
||||||
|
|
||||||
|
MAINTAINER datarhei <info@datarhei.org>
|
||||||
|
|
||||||
|
ENV NODE_VERSION=5.9.0 \
|
||||||
|
NPM_VERSION=3.7.3 \
|
||||||
|
|
||||||
|
FFMPEG_VERSION=2.8.6 \
|
||||||
|
YASM_VERSION=1.3.0 \
|
||||||
|
LAME_VERSION=3_99_5 \
|
||||||
|
NGINX_VERSION=1.9.9 \
|
||||||
|
NGINX_RTMP_VERSION=1.1.7.10 \
|
||||||
|
|
||||||
|
SRC="/usr/local" \
|
||||||
|
LD_LIBRARY_PATH="${SRC}/lib" \
|
||||||
|
PKG_CONFIG_PATH="${SRC}/lib/pkgconfig" \
|
||||||
|
|
||||||
|
BUILDDEPS="autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
||||||
|
|
||||||
|
RUN rm -rf /var/lib/apt/lists/* && \
|
||||||
|
apt-get update && \
|
||||||
|
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS} && \
|
||||||
|
|
||||||
|
# node
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
set -x && \
|
||||||
|
curl -LOks "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-arm64.tar.gz" && \
|
||||||
|
tar xzvf "node-v${NODE_VERSION}-linux-arm64.tar.gz" \
|
||||||
|
-C "${SRC}" \
|
||||||
|
--strip-components=1 && \
|
||||||
|
npm install -g "npm@${NPM_VERSION}" --unsafe-perm && \
|
||||||
|
npm cache clear && \
|
||||||
|
npm config set unsafe-perm true -g --unsafe-perm && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
|
# yasm
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
|
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
|
cd "yasm-${YASM_VERSION}" && \
|
||||||
|
cp /usr/share/automake-1.14/config.guess config.guess && \
|
||||||
|
./configure \
|
||||||
|
--prefix="${SRC}" \
|
||||||
|
--bindir="${SRC}/bin" && \
|
||||||
|
make -j"$(nproc)" && \
|
||||||
|
make install && \
|
||||||
|
make distclean && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
|
# x264
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||||
|
cd x264 && \
|
||||||
|
./configure \
|
||||||
|
--prefix="${SRC}" \
|
||||||
|
--bindir="${SRC}/bin" \
|
||||||
|
--enable-static \
|
||||||
|
--disable-cli && \
|
||||||
|
make -j"$(nproc)" && \
|
||||||
|
make install && \
|
||||||
|
make distclean && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
|
# libmp3lame
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
|
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
|
cd "lame-RELEASE__${LAME_VERSION}" && \
|
||||||
|
cp /usr/share/automake-1.14/config.guess config.guess && \
|
||||||
|
./configure \
|
||||||
|
--prefix="${SRC}" \
|
||||||
|
--bindir="${SRC}/bin" \
|
||||||
|
--enable-nasm \
|
||||||
|
--disable-shared && \
|
||||||
|
make -j"$(nproc)" && \
|
||||||
|
make install && \
|
||||||
|
make distclean && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
|
# ffmpeg
|
||||||
|
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
|
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
|
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||||
|
curl -Lks "https://github.com/FFmpeg/FFmpeg/commit/1c7e2cf9d33968375ee4025d2279c937e147dae2.patch" | patch -p1 && \
|
||||||
|
./configure \
|
||||||
|
--prefix="${SRC}" \
|
||||||
|
--bindir="${SRC}/bin" \
|
||||||
|
--extra-cflags="-I${SRC}/include" \
|
||||||
|
--extra-ldflags="-L${SRC}/lib" \
|
||||||
|
--extra-libs=-ldl \
|
||||||
|
--enable-nonfree \
|
||||||
|
--enable-gpl \
|
||||||
|
--enable-version3 \
|
||||||
|
--enable-avresample \
|
||||||
|
--enable-libmp3lame \
|
||||||
|
--enable-libx264 \
|
||||||
|
--enable-openssl \
|
||||||
|
--enable-postproc \
|
||||||
|
--enable-small \
|
||||||
|
--disable-debug \
|
||||||
|
--disable-doc \
|
||||||
|
--disable-ffserver && \
|
||||||
|
make -j"$(nproc)" && \
|
||||||
|
make install && \
|
||||||
|
make distclean && \
|
||||||
|
hash -r && \
|
||||||
|
cd tools && \
|
||||||
|
make qt-faststart && \
|
||||||
|
cp qt-faststart "${SRC}/bin" && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf" && \
|
||||||
|
ffmpeg -buildconf && \
|
||||||
|
|
||||||
|
# nginx-rtmp
|
||||||
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
|
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
||||||
|
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
||||||
|
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
||||||
|
tar xzvf "v${NGINX_RTMP_VERSION}.tar.gz" && \
|
||||||
|
cd "nginx-release-${NGINX_VERSION}" && \
|
||||||
|
auto/configure \
|
||||||
|
--with-http_ssl_module \
|
||||||
|
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
||||||
|
make -j"$(nproc)" && \
|
||||||
|
make install && \
|
||||||
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
|
apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||||
|
rm -rf /tmp/*
|
||||||
|
|
||||||
|
COPY . /restreamer
|
||||||
|
WORKDIR /restreamer
|
||||||
|
|
||||||
|
RUN npm install -g bower grunt grunt-cli nodemon public-ip && \
|
||||||
|
npm install && \
|
||||||
|
grunt build && \
|
||||||
|
npm prune --production && \
|
||||||
|
npm cache clean && \
|
||||||
|
bower cache clean --allow-root
|
||||||
|
|
||||||
|
ENV RS_USERNAME=admin \
|
||||||
|
RS_PASSWORD=datarhei
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
VOLUME ["/restreamer/db"]
|
||||||
|
|
||||||
|
CMD ["./run.sh"]
|
||||||
@@ -2,27 +2,27 @@ FROM resin/rpi-raspbian:jessie
|
|||||||
|
|
||||||
MAINTAINER datarhei <info@datarhei.org>
|
MAINTAINER datarhei <info@datarhei.org>
|
||||||
|
|
||||||
ENV NODE_VERSION 5.7.1
|
ENV NODE_VERSION=5.9.0 \
|
||||||
ENV NPM_VERSION 3.6.0
|
NPM_VERSION=3.7.3 \
|
||||||
|
|
||||||
ENV FFMPEG_VERSION 2.8.6
|
FFMPEG_VERSION=2.8.6 \
|
||||||
ENV YASM_VERSION 1.3.0
|
YASM_VERSION=1.3.0 \
|
||||||
ENV LAME_VERSION 3_99_5
|
LAME_VERSION=3_99_5 \
|
||||||
ENV NGINX_VERSION 1.9.9
|
NGINX_VERSION=1.9.9 \
|
||||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
NGINX_RTMP_VERSION=1.1.7.10 \
|
||||||
|
|
||||||
ENV SRC "/usr/local"
|
SRC="/usr/local" \
|
||||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
LD_LIBRARY_PATH="${SRC}/lib" \
|
||||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
PKG_CONFIG_PATH="${SRC}/lib/pkgconfig" \
|
||||||
|
|
||||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
BUILDDEPS="autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
||||||
|
|
||||||
RUN rm -rf /var/lib/apt/lists/* && \
|
RUN rm -rf /var/lib/apt/lists/* && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS} && \
|
||||||
|
|
||||||
# node
|
# node
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
set -x && \
|
set -x && \
|
||||||
curl -LOks "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-armv6l.tar.gz" && \
|
curl -LOks "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-armv6l.tar.gz" && \
|
||||||
tar xzvf "node-v${NODE_VERSION}-linux-armv6l.tar.gz" \
|
tar xzvf "node-v${NODE_VERSION}-linux-armv6l.tar.gz" \
|
||||||
@@ -31,10 +31,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
npm install -g "npm@${NPM_VERSION}" --unsafe-perm && \
|
npm install -g "npm@${NPM_VERSION}" --unsafe-perm && \
|
||||||
npm cache clear && \
|
npm cache clear && \
|
||||||
npm config set unsafe-perm true -g --unsafe-perm && \
|
npm config set unsafe-perm true -g --unsafe-perm && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# yasm
|
# yasm
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
cd "yasm-${YASM_VERSION}" && \
|
cd "yasm-${YASM_VERSION}" && \
|
||||||
@@ -44,10 +44,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# x264
|
# x264
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||||
cd x264 && \
|
cd x264 && \
|
||||||
./configure \
|
./configure \
|
||||||
@@ -58,10 +58,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# libmp3lame
|
# libmp3lame
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
cd "lame-RELEASE__${LAME_VERSION}" && \
|
cd "lame-RELEASE__${LAME_VERSION}" && \
|
||||||
@@ -73,11 +73,11 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# ffmpeg
|
# ffmpeg
|
||||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||||
@@ -107,12 +107,12 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
cd tools && \
|
cd tools && \
|
||||||
make qt-faststart && \
|
make qt-faststart && \
|
||||||
cp qt-faststart "${SRC}/bin" && \
|
cp qt-faststart "${SRC}/bin" && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf" && \
|
||||||
RUN ffmpeg -buildconf
|
ffmpeg -buildconf && \
|
||||||
|
|
||||||
# nginx-rtmp
|
# nginx-rtmp
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
||||||
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
||||||
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
||||||
@@ -123,9 +123,9 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
COPY . /restreamer
|
COPY . /restreamer
|
||||||
@@ -138,8 +138,8 @@ RUN npm install -g bower grunt grunt-cli nodemon public-ip && \
|
|||||||
npm cache clean && \
|
npm cache clean && \
|
||||||
bower cache clean --allow-root
|
bower cache clean --allow-root
|
||||||
|
|
||||||
ENV RS_USERNAME admin
|
ENV RS_USERNAME=admin \
|
||||||
ENV RS_PASSWORD datarhei
|
RS_PASSWORD=datarhei
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
VOLUME ["/restreamer/db"]
|
VOLUME ["/restreamer/db"]
|
||||||
|
|||||||
@@ -2,27 +2,27 @@ FROM resin/rpi-raspbian:jessie
|
|||||||
|
|
||||||
MAINTAINER datarhei <info@datarhei.org>
|
MAINTAINER datarhei <info@datarhei.org>
|
||||||
|
|
||||||
ENV NODE_VERSION 5.7.1
|
ENV NODE_VERSION=5.9.0 \
|
||||||
ENV NPM_VERSION 3.6.0
|
NPM_VERSION=3.7.3 \
|
||||||
|
|
||||||
ENV FFMPEG_VERSION 2.8.6
|
FFMPEG_VERSION=2.8.6 \
|
||||||
ENV YASM_VERSION 1.3.0
|
YASM_VERSION=1.3.0 \
|
||||||
ENV LAME_VERSION 3_99_5
|
LAME_VERSION=3_99_5 \
|
||||||
ENV NGINX_VERSION 1.9.9
|
NGINX_VERSION=1.9.9 \
|
||||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
NGINX_RTMP_VERSION=1.1.7.10 \
|
||||||
|
|
||||||
ENV SRC "/usr/local"
|
SRC="/usr/local" \
|
||||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
LD_LIBRARY_PATH="${SRC}/lib" \
|
||||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
PKG_CONFIG_PATH="${SRC}/lib/pkgconfig" \
|
||||||
|
|
||||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
BUILDDEPS="autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake build-essential libpcre3-dev"
|
||||||
|
|
||||||
RUN rm -rf /var/lib/apt/lists/* && \
|
RUN rm -rf /var/lib/apt/lists/* && \
|
||||||
apt-get update && \
|
apt-get update && \
|
||||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS} && \
|
||||||
|
|
||||||
# node
|
# node
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
set -x && \
|
set -x && \
|
||||||
curl -LOks "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-armv7l.tar.gz" && \
|
curl -LOks "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-armv7l.tar.gz" && \
|
||||||
tar xzvf "node-v${NODE_VERSION}-linux-armv7l.tar.gz" \
|
tar xzvf "node-v${NODE_VERSION}-linux-armv7l.tar.gz" \
|
||||||
@@ -31,10 +31,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
npm install -g "npm@${NPM_VERSION}" --unsafe-perm && \
|
npm install -g "npm@${NPM_VERSION}" --unsafe-perm && \
|
||||||
npm cache clear && \
|
npm cache clear && \
|
||||||
npm config set unsafe-perm true -g --unsafe-perm && \
|
npm config set unsafe-perm true -g --unsafe-perm && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# yasm
|
# yasm
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
curl -LOks "https://www.tortall.net/projects/yasm/releases/yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
tar xzvf "yasm-${YASM_VERSION}.tar.gz" && \
|
||||||
cd "yasm-${YASM_VERSION}" && \
|
cd "yasm-${YASM_VERSION}" && \
|
||||||
@@ -44,10 +44,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# x264
|
# x264
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||||
cd x264 && \
|
cd x264 && \
|
||||||
./configure \
|
./configure \
|
||||||
@@ -58,10 +58,10 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# libmp3lame
|
# libmp3lame
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/rbrito/lame/archive/RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
tar xzvf "RELEASE__${LAME_VERSION}.tar.gz" && \
|
||||||
cd "lame-RELEASE__${LAME_VERSION}" && \
|
cd "lame-RELEASE__${LAME_VERSION}" && \
|
||||||
@@ -73,11 +73,11 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
make distclean && \
|
make distclean && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
# ffmpeg
|
# ffmpeg
|
||||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||||
@@ -107,12 +107,12 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
cd tools && \
|
cd tools && \
|
||||||
make qt-faststart && \
|
make qt-faststart && \
|
||||||
cp qt-faststart "${SRC}/bin" && \
|
cp qt-faststart "${SRC}/bin" && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf" && \
|
||||||
RUN ffmpeg -buildconf
|
ffmpeg -buildconf && \
|
||||||
|
|
||||||
# nginx-rtmp
|
# nginx-rtmp
|
||||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||||
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/nginx/nginx/archive/release-${NGINX_VERSION}.tar.gz" && \
|
||||||
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
tar xzvf "release-${NGINX_VERSION}.tar.gz" && \
|
||||||
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
curl -LOks "https://github.com/sergey-dryabzhinsky/nginx-rtmp-module/archive/v${NGINX_RTMP_VERSION}.tar.gz" && \
|
||||||
@@ -123,9 +123,9 @@ RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
|||||||
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
--add-module="../nginx-rtmp-module-${NGINX_RTMP_VERSION}" && \
|
||||||
make -j"$(nproc)" && \
|
make -j"$(nproc)" && \
|
||||||
make install && \
|
make install && \
|
||||||
rm -rf "${DIR}"
|
rm -rf "${DIR}" && \
|
||||||
|
|
||||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||||
rm -rf /tmp/*
|
rm -rf /tmp/*
|
||||||
|
|
||||||
COPY . /restreamer
|
COPY . /restreamer
|
||||||
@@ -138,8 +138,8 @@ RUN npm install -g bower grunt grunt-cli nodemon public-ip && \
|
|||||||
npm cache clean && \
|
npm cache clean && \
|
||||||
bower cache clean --allow-root
|
bower cache clean --allow-root
|
||||||
|
|
||||||
ENV RS_USERNAME admin
|
ENV RS_USERNAME=admin \
|
||||||
ENV RS_PASSWORD datarhei
|
RS_PASSWORD=datarhei
|
||||||
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
VOLUME ["/restreamer/db"]
|
VOLUME ["/restreamer/db"]
|
||||||
|
|||||||
@@ -13,14 +13,14 @@ Datarhei/Restreamer offers smart free video streaming in real time. Stream H.264
|
|||||||
|
|
||||||
## Upcomming releases
|
## Upcomming releases
|
||||||
|
|
||||||
- RC7 (tba)
|
- rc.8 (next 14 days)
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
- optimizing FFmpeg handling
|
|
||||||
- backend refactoring
|
- backend refactoring
|
||||||
- full REST API
|
- full REST API
|
||||||
- security improvements
|
- optimizing FFmpeg handling
|
||||||
|
- debugging features
|
||||||
|
|
||||||
##Documentation
|
##Documentation
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Restreamer",
|
"name": "Restreamer",
|
||||||
"version": "0.1.0-RC6.1",
|
"version": "0.1.0-rc.7",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bootstrap": "3.3.6",
|
"bootstrap": "3.3.6",
|
||||||
"jquery": "2.2.1",
|
"jquery": "2.2.2",
|
||||||
"html5shiv": "3.7.3",
|
"html5shiv": "3.7.3",
|
||||||
"respond": "1.4.2",
|
"respond": "1.4.2",
|
||||||
"angular-bootstrap": "~0.14.3",
|
"angular-bootstrap": "~0.14.3",
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
"angular-translate-loader-static-files": "2.9.2"
|
"angular-translate-loader-static-files": "2.9.2"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"angular": "1.4.8",
|
"angular": "1.5.0"
|
||||||
"angular-bootstrap": "~0.14.3"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ rtmp {
|
|||||||
chunk_size 4000;
|
chunk_size 4000;
|
||||||
application live {
|
application live {
|
||||||
live on;
|
live on;
|
||||||
meta copy;
|
idle_streams off;
|
||||||
}
|
}
|
||||||
application hls {
|
application hls {
|
||||||
live on;
|
live on;
|
||||||
@@ -18,6 +18,7 @@ rtmp {
|
|||||||
hls_playlist_length 60s;
|
hls_playlist_length 60s;
|
||||||
hls_fragment 2s;
|
hls_fragment 2s;
|
||||||
hls_path /tmp/hls;
|
hls_path /tmp/hls;
|
||||||
|
idle_streams off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -26,13 +27,14 @@ http {
|
|||||||
tcp_nopush on;
|
tcp_nopush on;
|
||||||
server {
|
server {
|
||||||
listen 8080;
|
listen 8080;
|
||||||
location ~ ^/(libs|locales|images|dist|help|css|scripts|player.html|crossdomain.xml) {
|
root /restreamer/src/webserver/public;
|
||||||
root /restreamer/src/webserver/public;
|
include /usr/local/nginx/conf/mime.types;
|
||||||
allow all;
|
location / {
|
||||||
include /usr/local/nginx/conf/mime.types;
|
try_files $uri @node;
|
||||||
add_header Access-Control-Allow-Origin *;
|
add_header Access-Control-Allow-Origin *;
|
||||||
}
|
}
|
||||||
location / {
|
location @node {
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
proxy_pass http://127.0.0.1:3000;
|
proxy_pass http://127.0.0.1:3000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
|||||||
22
package.json
22
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "Restreamer",
|
"name": "Restreamer",
|
||||||
"version": "0.1.0-RC6.1",
|
"version": "0.1.0-rc.7",
|
||||||
"description": "Allows you to do h.264 real-time video streaming on your website without a streaming provider",
|
"description": "Allows you to do h.264 real-time video streaming on your website without a streaming provider",
|
||||||
"author": "datarhei.org",
|
"author": "datarhei.org",
|
||||||
"repository": {
|
"repository": {
|
||||||
@@ -14,30 +14,30 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "1.15.0",
|
"body-parser": "1.15.0",
|
||||||
"compression": "~1.6.1",
|
"compression": "~1.6.1",
|
||||||
|
"cookie": "^0.2.3",
|
||||||
"cookie-parser": "1.4.1",
|
"cookie-parser": "1.4.1",
|
||||||
"express": "4.13.4",
|
"express": "4.13.4",
|
||||||
"express-session": "^1.13.0",
|
"express-session": "^1.13.0",
|
||||||
"fluent-ffmpeg": "git://github.com/datarhei/node-fluent-ffmpeg",
|
"fluent-ffmpeg": "git://github.com/datarhei/node-fluent-ffmpeg",
|
||||||
"jsonschema": "^1.1.0",
|
"jsonschema": "^1.1.0",
|
||||||
"moment-timezone": "^0.5.0",
|
"moment-timezone": "^0.5.2",
|
||||||
"node-json-db": "git://github.com/andrew-shulgin/node-json-db",
|
"node-json-db": "git://github.com/andrew-shulgin/node-json-db",
|
||||||
"passport": "^0.3.2",
|
"semver": "^5.1.0",
|
||||||
"passport-local": "^1.0.0",
|
|
||||||
"ps-find": "^1.1.0",
|
"ps-find": "^1.1.0",
|
||||||
"q": "1.4.1",
|
"q": "1.4.1",
|
||||||
"socket.io": "1.4.5"
|
"socket.io": "1.4.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"eslint": "2.3.0",
|
"eslint": "2.4.0",
|
||||||
"babel-preset-es2015": "^6.5.0",
|
"babel-preset-es2015": "^6.6.0",
|
||||||
"grunt": "0.4.5",
|
"grunt": "0.4.5",
|
||||||
"grunt-babel": "^6.0.0",
|
"grunt-babel": "^6.0.0",
|
||||||
"grunt-contrib-csslint": "^1.0.0",
|
"grunt-contrib-csslint": "^1.0.0",
|
||||||
"grunt-contrib-cssmin": "~1.0.0",
|
"grunt-contrib-cssmin": "~1.0.1",
|
||||||
"grunt-contrib-uglify": "~1.0.0",
|
"grunt-contrib-uglify": "~1.0.1",
|
||||||
"grunt-contrib-watch": "^0.6.1",
|
"grunt-contrib-watch": "^1.0.0",
|
||||||
"grunt-ng-annotate": "~1.0.1",
|
"grunt-ng-annotate": "~2.0.1",
|
||||||
"grunt-shell": "1.2.1",
|
"grunt-shell": "1.2.1",
|
||||||
"load-grunt-tasks": "~3.4.0"
|
"load-grunt-tasks": "~3.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
run.sh
31
run.sh
@@ -15,7 +15,36 @@ then
|
|||||||
sleep 5
|
sleep 5
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
/opt/vc/bin/raspivid -t 0 -w 1280 -h 720 -fps 25 -b 500000 -o - | ffmpeg -i - -f lavfi -i aevalsrc=0 -vcodec copy -acodec aac -strict experimental -map 0:0 -map 1:0 -shortest -flags +global_header -f flv rtmp://127.0.0.1:1935/live/raspicam.stream > /dev/null 2>&1
|
|
||||||
|
if [ "$RS_RASPICAM_WIDTH" = "" ]
|
||||||
|
then
|
||||||
|
RASPICAM_WIDTH=1280
|
||||||
|
else
|
||||||
|
RASPICAM_WIDTH=$RS_RASPICAM_WIDTH
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RS_RASPICAM_HEIGHT" = "" ]
|
||||||
|
then
|
||||||
|
RASPICAM_HEIGHT=720
|
||||||
|
else
|
||||||
|
RASPICAM_HEIGHT=$RS_RASPICAM_HEIGHT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RS_RASPICAM_FPS" = "" ]
|
||||||
|
then
|
||||||
|
RASPICAM_FPS=25
|
||||||
|
else
|
||||||
|
RASPICAM_FPS=$RS_RASPICAM_FPS
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$RS_RASPICAM_BITRATE" = "" ]
|
||||||
|
then
|
||||||
|
RASPICAM_BITRATE=50000
|
||||||
|
else
|
||||||
|
RASPICAM_BITRATE=$RS_RASPICAM_BITRATE
|
||||||
|
fi
|
||||||
|
|
||||||
|
/opt/vc/bin/raspivid -t 0 -w $RASPICAM_WIDTH -h $RASPICAM_HEIGHT -fps $RASPICAM_FPS -b $RASPICAM_BITRATE -o - | ffmpeg -i - -f lavfi -i aevalsrc=0 -vcodec copy -acodec aac -strict experimental -map 0:0 -map 1:0 -shortest -flags +global_header -f flv rtmp://127.0.0.1:1935/live/raspicam.stream > /dev/null 2>&1
|
||||||
elif [ "${MODE}" == "USBCAM" ];
|
elif [ "${MODE}" == "USBCAM" ];
|
||||||
then
|
then
|
||||||
apt-get update && apt-get install -y v4l-utils libv4l-0
|
apt-get update && apt-get install -y v4l-utils libv4l-0
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
const logger = require('./Logger')('EnvVar');
|
const logger = require('./Logger')('EnvVar');
|
||||||
|
const logBlacklist = ['RS_PASSWORD'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class for environment variables with default values
|
* Class for environment variables with default values
|
||||||
@@ -19,15 +20,14 @@ class EnvVar {
|
|||||||
process.env[envVar.name] = process.env[envVar.alias];
|
process.env[envVar.name] = process.env[envVar.alias];
|
||||||
delete process.env[envVar.alias];
|
delete process.env[envVar.alias];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof process.env[envVar.name] !== 'undefined') {
|
if (typeof process.env[envVar.name] !== 'undefined') {
|
||||||
logger.info(`ENV "${envVar.name} = ${process.env[envVar.name]}"`, envVar.description);
|
logger.info(`ENV "${envVar.name} = ${(logBlacklist.indexOf(envVar.name) === -1 ? process.env[envVar.name] : '[hidden]')}"`, envVar.description);
|
||||||
} else if (envVar.required === true) {
|
} else if (envVar.required === true) {
|
||||||
logger.error(`No value set for env "${envVar.name}", but it is required`);
|
logger.error(`No value set for env "${envVar.name}", but it is required`);
|
||||||
killProcess = true;
|
killProcess = true;
|
||||||
} else {
|
} else {
|
||||||
process.env[envVar.name] = envVar.defaultValue;
|
process.env[envVar.name] = envVar.defaultValue;
|
||||||
logger.info(`ENV "${envVar.name} = ${process.env[envVar.name]}", set to default value`, envVar.description);
|
logger.info(`ENV "${envVar.name} = ${(logBlacklist.indexOf(envVar.name) === -1 ? process.env[envVar.name] : '[hidden]')}", set to default value`, envVar.description);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof process.env[envVar.name] !== 'undefined') {
|
if (typeof process.env[envVar.name] !== 'undefined') {
|
||||||
|
|||||||
@@ -12,8 +12,10 @@ const logger = require('./Logger')('Restreamer');
|
|||||||
const WebsocketsController = require('./WebsocketsController');
|
const WebsocketsController = require('./WebsocketsController');
|
||||||
const FfmpegCommand = require('fluent-ffmpeg');
|
const FfmpegCommand = require('fluent-ffmpeg');
|
||||||
const Q = require('q');
|
const Q = require('q');
|
||||||
const app = require.main.require('./webserver/app').app;
|
|
||||||
const JsonDB = require('node-json-db');
|
const JsonDB = require('node-json-db');
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const packageJson = require(path.join(global.__base, 'package.json'));
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class Restreamer creates and manages streams through ffmpeg
|
* class Restreamer creates and manages streams through ffmpeg
|
||||||
@@ -40,11 +42,10 @@ class Restreamer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* receive snapshot by using first frame of repeated video
|
* receive snapshot by using first frame of repeated video
|
||||||
* @param {boolean} firstSnapshot
|
|
||||||
*/
|
*/
|
||||||
static fetchSnapshot (firstSnapshot) {
|
static fetchSnapshot () {
|
||||||
var command = null;
|
var command = null;
|
||||||
if (Restreamer.data.states.repeatToLocalNginx.type === 'connected' || firstSnapshot) {
|
if (Restreamer.data.states.repeatToLocalNginx.type === 'connected') {
|
||||||
command = new FfmpegCommand(Restreamer.generateOutputHLSPath());
|
command = new FfmpegCommand(Restreamer.generateOutputHLSPath());
|
||||||
|
|
||||||
command.output(Restreamer.generateSnapshotPath());
|
command.output(Restreamer.generateSnapshotPath());
|
||||||
@@ -53,9 +54,10 @@ class Restreamer {
|
|||||||
logger.error('Error on fetching snapshot: ' + error.toString());
|
logger.error('Error on fetching snapshot: ' + error.toString());
|
||||||
});
|
});
|
||||||
command.on('end', () => {
|
command.on('end', () => {
|
||||||
logger.info('updated snapshot');
|
logger.info('Updated snapshot');
|
||||||
|
WebsocketsController.emit('snapshot', null);
|
||||||
Q.delay(this.calculateSnapshotRefreshInterval()).then(() => {
|
Q.delay(this.calculateSnapshotRefreshInterval()).then(() => {
|
||||||
Restreamer.fetchSnapshot(false);
|
Restreamer.fetchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
command.exec();
|
command.exec();
|
||||||
@@ -70,7 +72,7 @@ class Restreamer {
|
|||||||
return snapshotRefreshInterval[1];
|
return snapshotRefreshInterval[1];
|
||||||
} else if (snapshotRefreshInterval[2] === 'm') {
|
} else if (snapshotRefreshInterval[2] === 'm') {
|
||||||
return snapshotRefreshInterval[1] * 1000 * 60;
|
return snapshotRefreshInterval[1] * 1000 * 60;
|
||||||
} else if (snapshotRefreshInterval[2] === 's' && snapshotRefreshInterval[1] > 30) {
|
} else if (snapshotRefreshInterval[2] === 's' && snapshotRefreshInterval[1] >= 10) {
|
||||||
return snapshotRefreshInterval[1] * 1000;
|
return snapshotRefreshInterval[1] * 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +88,7 @@ class Restreamer {
|
|||||||
|
|
||||||
Restreamer.updateState(processName, 'stopped');
|
Restreamer.updateState(processName, 'stopped');
|
||||||
logger.info('stopStream ' + processName);
|
logger.info('stopStream ' + processName);
|
||||||
|
clearTimeout(Restreamer.data.retryTimeouts[processName]);
|
||||||
if (processHasBeenSpawned) {
|
if (processHasBeenSpawned) {
|
||||||
Restreamer.data.processes[processName].kill();
|
Restreamer.data.processes[processName].kill();
|
||||||
Restreamer.data.processes[processName] = {
|
Restreamer.data.processes[processName] = {
|
||||||
@@ -146,14 +149,14 @@ class Restreamer {
|
|||||||
* send websocket event to GUI to update the state of the streams
|
* send websocket event to GUI to update the state of the streams
|
||||||
*/
|
*/
|
||||||
static updateStreamDataOnGui () {
|
static updateStreamDataOnGui () {
|
||||||
WebsocketsController.emitToNamespace('/', 'updateStreamData', Restreamer.extractDataOfStreams());
|
WebsocketsController.emit('updateStreamData', Restreamer.extractDataOfStreams());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* send websocket event to GUI to update the state of the streams
|
* send websocket event to GUI to update the state of the streams
|
||||||
*/
|
*/
|
||||||
static updateProgressOnGui () {
|
static updateProgressOnGui () {
|
||||||
WebsocketsController.emitToNamespace('/', 'updateProgress', Restreamer.data.progresses);
|
WebsocketsController.emit('updateProgress', Restreamer.data.progresses);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -168,6 +171,7 @@ class Restreamer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static applyOptions (ffmpegCommand, streamType) {
|
static applyOptions (ffmpegCommand, streamType) {
|
||||||
|
ffmpegCommand.native(); // add -re
|
||||||
if (streamType === 'repeatToLocalNginx') {
|
if (streamType === 'repeatToLocalNginx') {
|
||||||
if (Restreamer.data.options.rtspTcp && Restreamer.data.addresses.srcAddress.indexOf('rtsp') === 0) {
|
if (Restreamer.data.options.rtspTcp && Restreamer.data.addresses.srcAddress.indexOf('rtsp') === 0) {
|
||||||
ffmpegCommand.inputOptions('-rtsp_transport tcp');
|
ffmpegCommand.inputOptions('-rtsp_transport tcp');
|
||||||
@@ -217,6 +221,9 @@ class Restreamer {
|
|||||||
'type': state,
|
'type': state,
|
||||||
'message': message
|
'message': message
|
||||||
};
|
};
|
||||||
|
if (processName === 'repeatToLocalNginx' && state === 'connected') {
|
||||||
|
Restreamer.fetchSnapshot();
|
||||||
|
}
|
||||||
Restreamer.writeToDB();
|
Restreamer.writeToDB();
|
||||||
Restreamer.updateStreamDataOnGui();
|
Restreamer.updateStreamDataOnGui();
|
||||||
return state;
|
return state;
|
||||||
@@ -315,7 +322,6 @@ class Restreamer {
|
|||||||
};
|
};
|
||||||
Restreamer.applyOptions(command, streamType);
|
Restreamer.applyOptions(command, streamType);
|
||||||
command
|
command
|
||||||
// stream started
|
|
||||||
.on('start', (commandLine) => {
|
.on('start', (commandLine) => {
|
||||||
if (Restreamer.data.userActions[streamType] === 'stop') {
|
if (Restreamer.data.userActions[streamType] === 'stop') {
|
||||||
logger.debug('Skipping on "start" event of FFmpeg command since "stopped" has been clicked');
|
logger.debug('Skipping on "start" event of FFmpeg command since "stopped" has been clicked');
|
||||||
@@ -323,11 +329,6 @@ class Restreamer {
|
|||||||
}
|
}
|
||||||
logger.debug(`FFmpeg spawned: ${commandLine}`);
|
logger.debug(`FFmpeg spawned: ${commandLine}`);
|
||||||
Restreamer.data.processes[streamType] = command;
|
Restreamer.data.processes[streamType] = command;
|
||||||
|
|
||||||
// fetch snapshot only, if repeated to local nginx
|
|
||||||
if (repeatToLocalNginx) {
|
|
||||||
Restreamer.fetchSnapshot(true);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// stream ended
|
// stream ended
|
||||||
@@ -340,10 +341,10 @@ class Restreamer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
||||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(() => {
|
Restreamer.data.retryTimeouts[streamType] = setTimeout(() => {
|
||||||
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||||
});
|
}, config.ffmpeg.monitor.restart_wait);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -362,10 +363,10 @@ class Restreamer {
|
|||||||
Restreamer.updateState(streamType, 'error', error.toString());
|
Restreamer.updateState(streamType, 'error', error.toString());
|
||||||
logger.error(`Error on stream ${streamType}: ${error.toString()}`);
|
logger.error(`Error on stream ${streamType}: ${error.toString()}`);
|
||||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
||||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(() => {
|
Restreamer.data.retryTimeouts[streamType] = setTimeout(() => {
|
||||||
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||||
});
|
}, config.ffmpeg.monitor.restart_wait);
|
||||||
} else {
|
} else {
|
||||||
Restreamer.updateState(streamType, 'error', error.toString());
|
Restreamer.updateState(streamType, 'error', error.toString());
|
||||||
}
|
}
|
||||||
@@ -388,7 +389,8 @@ class Restreamer {
|
|||||||
* bind websocket events on application start
|
* bind websocket events on application start
|
||||||
*/
|
*/
|
||||||
static bindWebsocketEvents () {
|
static bindWebsocketEvents () {
|
||||||
WebsocketsController.addOnConnectionEventToNamespace('/', (socket) => {
|
WebsocketsController.setConnectCallback((socket) => {
|
||||||
|
socket.emit('publicIp', Restreamer.data.publicIp);
|
||||||
socket.on('startStream', (options)=> {
|
socket.on('startStream', (options)=> {
|
||||||
Restreamer.updateUserAction(options.streamType, 'start');
|
Restreamer.updateUserAction(options.streamType, 'start');
|
||||||
Restreamer.updateOptions(options.options);
|
Restreamer.updateOptions(options.options);
|
||||||
@@ -398,9 +400,6 @@ class Restreamer {
|
|||||||
Restreamer.updateUserAction(streamType, 'stop');
|
Restreamer.updateUserAction(streamType, 'stop');
|
||||||
Restreamer.stopStream(streamType);
|
Restreamer.stopStream(streamType);
|
||||||
});
|
});
|
||||||
socket.on('checkForAppUpdates', ()=> {
|
|
||||||
socket.emit('checkForAppUpdatesResult', app.get('updateAvailable'));
|
|
||||||
});
|
|
||||||
socket.on('checkStates', Restreamer.updateStreamDataOnGui);
|
socket.on('checkStates', Restreamer.updateStreamDataOnGui);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -432,6 +431,44 @@ class Restreamer {
|
|||||||
'states': Restreamer.data.states
|
'states': Restreamer.data.states
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check for updates
|
||||||
|
*/
|
||||||
|
static checkForUpdates () {
|
||||||
|
const url = {'host': 'datarhei.org', 'path': '/apps.json'};
|
||||||
|
logger.debug('Checking for updates...');
|
||||||
|
https.get(url, (response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
response.on('data', (body) => {
|
||||||
|
var updateCheck = JSON.parse(body);
|
||||||
|
var updateAvailable = require('semver').lt(packageJson.version, updateCheck.restreamer.version);
|
||||||
|
logger.info(`Update checking succeeded. ${updateAvailable ? 'Update' : 'No updates'} available`, 'checkForUpdates');
|
||||||
|
logger.debug(`local: ${packageJson.version}; remote: ${updateCheck.restreamer.version}`, 'checkForUpdates');
|
||||||
|
Restreamer.data.updateAvailable = updateAvailable;
|
||||||
|
WebsocketsController.emit('update', updateAvailable);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.warn(`Got ${String(response.statusCode)} status while trying to fetch update info`, 'checkForUpdates');
|
||||||
|
}
|
||||||
|
}).on('error', () => {
|
||||||
|
logger.warn('Failed fetching update info', 'checkForUpdates');
|
||||||
|
});
|
||||||
|
setTimeout(Restreamer.checkForUpdates, 12 * 3600 * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get public ip
|
||||||
|
*/
|
||||||
|
static getPublicIp () {
|
||||||
|
logger.info('Getting public ip...', 'start.publicip');
|
||||||
|
exec('public-ip', (err, stdout, stderr) => {
|
||||||
|
if (err) {
|
||||||
|
logger.error(err);
|
||||||
|
}
|
||||||
|
Restreamer.data.publicIp = stdout.split('\n')[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -448,6 +485,10 @@ Restreamer.data = {
|
|||||||
'max': config.ffmpeg.monitor.retries
|
'max': config.ffmpeg.monitor.retries
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
'retryTimeouts': {
|
||||||
|
'repeatToLocalNginx': null,
|
||||||
|
'repeatToOptionalOutput': null
|
||||||
|
},
|
||||||
'options': {
|
'options': {
|
||||||
'rtspTcp': false
|
'rtspTcp': false
|
||||||
},
|
},
|
||||||
@@ -488,7 +529,9 @@ Restreamer.data = {
|
|||||||
'addresses': {
|
'addresses': {
|
||||||
'srcAddress': '',
|
'srcAddress': '',
|
||||||
'optionalOutputAddress': ''
|
'optionalOutputAddress': ''
|
||||||
}
|
},
|
||||||
|
'updateAvailable': false,
|
||||||
|
'publicIp': '127.0.0.1'
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Restreamer;
|
module.exports = Restreamer;
|
||||||
|
|||||||
@@ -8,55 +8,36 @@
|
|||||||
|
|
||||||
const logger = require.main.require('./classes/Logger')('WebsocketsController');
|
const logger = require.main.require('./classes/Logger')('WebsocketsController');
|
||||||
const app = require.main.require('./webserver/app').app;
|
const app = require.main.require('./webserver/app').app;
|
||||||
const packageJson = require(require('path').join(global.__base, 'package.json'));
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* static class websocket controller, that helps communicating through websockets to different namespaces and ensures
|
* static class websocket controller, that helps communicating through websockets to different namespaces and ensures
|
||||||
* that websocket events are bound, if the websocket server has been initialized (through promise made on app start)
|
* that websocket events are bound, if the websocket server has been initialized (through promise made on app start)
|
||||||
* @todo since currently the Restreamer is a single page application, there is no need to use different namespaces
|
|
||||||
*/
|
*/
|
||||||
class WebsocketsController {
|
class WebsocketsController {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* emit an event to WS
|
||||||
* @param {string} namespace namespace to emit the event to
|
|
||||||
* @param {string} event name of the event
|
* @param {string} event name of the event
|
||||||
* @param {object} data data to emit to the client event listener
|
* @param {object} data data to emit to the client event listener
|
||||||
*/
|
*/
|
||||||
static emitToNamespace (namespace, event, data) {
|
static emit (event, data) {
|
||||||
app.get('websocketsReady').promise.then((io) => {
|
app.get('websocketsReady').promise.then((io) => {
|
||||||
logger.debug(`websocket got event ${event} to namespace ${namespace}`, 'Websockets');
|
logger.debug(`Emitting ${event}`);
|
||||||
io.of(namespace).emit(event, data);
|
io.sockets.emit(event, data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add event, that is emmi
|
* add callback on WS connection
|
||||||
* @param {string} namespace
|
|
||||||
* @param {function} callback
|
* @param {function} callback
|
||||||
*/
|
*/
|
||||||
static addOnConnectionEventToNamespace (namespace, callback) {
|
static setConnectCallback (callback) {
|
||||||
app.get('websocketsReady').promise.then((io) => {
|
app.get('websocketsReady').promise.then((io) => {
|
||||||
var nsp = io.of(namespace);
|
io.on('connection', (socket) => {
|
||||||
|
|
||||||
nsp.on('connection', (socket) => {
|
|
||||||
callback(socket);
|
callback(socket);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* bind default events of all classes that are using websockets events
|
|
||||||
*/
|
|
||||||
static bindDefaultEvents () {
|
|
||||||
WebsocketsController.addOnConnectionEventToNamespace('/', (socket) => {
|
|
||||||
socket.on('getVersion', () => {
|
|
||||||
socket.emit('version', packageJson.version);
|
|
||||||
});
|
|
||||||
socket.emit('publicIp', app.get('publicIp'));
|
|
||||||
});
|
|
||||||
require('./Restreamer').bindWebsocketEvents();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = WebsocketsController;
|
module.exports = WebsocketsController;
|
||||||
|
|||||||
19
src/start.js
19
src/start.js
@@ -19,7 +19,6 @@ const nginxrtmp = require('./classes/Nginxrtmp')(config);
|
|||||||
const Q = require('q');
|
const Q = require('q');
|
||||||
const Restreamer = require('./classes/Restreamer');
|
const Restreamer = require('./classes/Restreamer');
|
||||||
const RestreamerData = require('./classes/RestreamerData');
|
const RestreamerData = require('./classes/RestreamerData');
|
||||||
const WebsocketsController = require('./classes/WebsocketsController');
|
|
||||||
const restreamerApp = require('./webserver/app');
|
const restreamerApp = require('./webserver/app');
|
||||||
|
|
||||||
// show start message
|
// show start message
|
||||||
@@ -38,28 +37,20 @@ logger.info('', false);
|
|||||||
// setup environment vars
|
// setup environment vars
|
||||||
EnvVar.init(config);
|
EnvVar.init(config);
|
||||||
|
|
||||||
// check for app updates
|
|
||||||
restreamerApp.checkForRestreamerUpdates();
|
|
||||||
// Check for updates each 12 hours
|
|
||||||
setInterval(restreamerApp.checkForRestreamerUpdates, 12 * 3600 * 1000);
|
|
||||||
|
|
||||||
// add default websocket events, @todo this will be removed, when the new websocket workflow is implemented
|
|
||||||
WebsocketsController.bindDefaultEvents();
|
|
||||||
|
|
||||||
// start the app
|
// start the app
|
||||||
nginxrtmp.init()
|
nginxrtmp.init()
|
||||||
.then(()=> {
|
.then(() => {
|
||||||
return RestreamerData.checkJSONDb();
|
return RestreamerData.checkJSONDb();
|
||||||
})
|
})
|
||||||
.then(()=> {
|
.then(() => {
|
||||||
|
Restreamer.checkForUpdates();
|
||||||
|
Restreamer.getPublicIp();
|
||||||
|
Restreamer.bindWebsocketEvents();
|
||||||
return restreamerApp.startWebserver();
|
return restreamerApp.startWebserver();
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
return Q.fcall(Restreamer.restoreFFMpegProcesses);
|
return Q.fcall(Restreamer.restoreFFMpegProcesses);
|
||||||
})
|
})
|
||||||
.then(()=> {
|
|
||||||
restreamerApp.getPublicIp();
|
|
||||||
})
|
|
||||||
.catch((error)=> {
|
.catch((error)=> {
|
||||||
let errorMessage = `Error starting webserver and nginx for application: ${error}`;
|
let errorMessage = `Error starting webserver and nginx for application: ${error}`;
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
|
|||||||
@@ -5,26 +5,20 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// auth stuff
|
|
||||||
const passport = require('passport');
|
|
||||||
const passportConfig = require('./config/passport');
|
|
||||||
|
|
||||||
// express
|
// express
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const session = require('express-session');
|
const session = require('express-session');
|
||||||
|
const cookie = require('cookie');
|
||||||
const cookieParser = require('cookie-parser');
|
const cookieParser = require('cookie-parser');
|
||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const compression = require('compression');
|
const compression = require('compression');
|
||||||
const https = require('https');
|
|
||||||
|
|
||||||
// other
|
// other
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const Q = require('q');
|
const Q = require('q');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const exec = require('child_process').exec;
|
|
||||||
|
|
||||||
// modules
|
// modules
|
||||||
const packageJson = require(path.join(global.__base, 'package.json'));
|
|
||||||
const logger = require.main.require('./classes/Logger')('RestreamerExpressApp');
|
const logger = require.main.require('./classes/Logger')('RestreamerExpressApp');
|
||||||
const indexRouter = require('./controllers/index');
|
const indexRouter = require('./controllers/index');
|
||||||
const apiV1 = require('./controllers/api/v1');
|
const apiV1 = require('./controllers/api/v1');
|
||||||
@@ -43,6 +37,8 @@ class RestreamerExpressApp {
|
|||||||
constructor () {
|
constructor () {
|
||||||
this.app = express();
|
this.app = express();
|
||||||
this.secretKey = crypto.randomBytes(16).toString('hex');
|
this.secretKey = crypto.randomBytes(16).toString('hex');
|
||||||
|
this.sessionKey = 'restreamer-session';
|
||||||
|
this.sessionStore = new session.MemoryStore();
|
||||||
|
|
||||||
if (process.env.RS_NODE_ENV === 'dev') {
|
if (process.env.RS_NODE_ENV === 'dev') {
|
||||||
this.initDev();
|
this.initDev();
|
||||||
@@ -56,33 +52,20 @@ class RestreamerExpressApp {
|
|||||||
*/
|
*/
|
||||||
useSessions () {
|
useSessions () {
|
||||||
this.app.use(session({
|
this.app.use(session({
|
||||||
|
'resave': true,
|
||||||
|
'saveUninitialized': false,
|
||||||
|
'key': this.sessionKey,
|
||||||
'secret': this.secretKey,
|
'secret': this.secretKey,
|
||||||
'resave': false,
|
'unset': 'destroy',
|
||||||
'saveUninitialized': true // session secret
|
'store': this.sessionStore
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* use passport auth
|
|
||||||
*/
|
|
||||||
useAuth () {
|
|
||||||
// add passport auth
|
|
||||||
this.app.use(passport.initialize());
|
|
||||||
|
|
||||||
// persistent login sessions
|
|
||||||
this.app.use(passport.session());
|
|
||||||
|
|
||||||
// add config to passport
|
|
||||||
passportConfig(passport);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* add automatic parsers for the body
|
* add automatic parsers for the body
|
||||||
*/
|
*/
|
||||||
addParsers () {
|
addParsers () {
|
||||||
this.app.use(bodyParser.json());
|
this.app.use(bodyParser.json());
|
||||||
this.app.use(bodyParser.urlencoded({'extended': true}));
|
|
||||||
this.app.use(cookieParser());
|
this.app.use(cookieParser());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +101,7 @@ class RestreamerExpressApp {
|
|||||||
* add the restreamer routes
|
* add the restreamer routes
|
||||||
*/
|
*/
|
||||||
addRoutes () {
|
addRoutes () {
|
||||||
indexRouter(this.app, passport);
|
indexRouter(this.app);
|
||||||
this.app.use('/v1', apiV1);
|
this.app.use('/v1', apiV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -148,44 +131,21 @@ class RestreamerExpressApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* check for app updates
|
* enable websocket session validation
|
||||||
*/
|
*/
|
||||||
checkForRestreamerUpdates () {
|
secureSockets () {
|
||||||
const url = {'host': 'datarhei.org', 'path': '/apps.json'};
|
this.app.get('io').set('authorization', (handshakeData, accept) => {
|
||||||
logger.debug('Checking app for updates...');
|
if (handshakeData.headers.cookie) {
|
||||||
https.get(url, (response)=> {
|
this.sessionStore.get(cookieParser.signedCookie(
|
||||||
if (response.statusCode === 200) {
|
cookie.parse(handshakeData.headers.cookie)[this.sessionKey], this.secretKey
|
||||||
response.on('data', (body)=> {
|
), (err, s) => {
|
||||||
var updateCheck = JSON.parse(body);
|
if (!err && s && s.authenticated) {
|
||||||
var updateAvailable = false;
|
return accept(null, true);
|
||||||
if (updateCheck.restreamer.version === packageJson.version) {
|
|
||||||
updateAvailable = false;
|
|
||||||
logger.debug(`Checking app for updates successful. Update is not available (remote: ${updateCheck.restreamer.version}, local: ${packageJson.version})`);
|
|
||||||
} else {
|
|
||||||
updateAvailable = updateCheck.restreamer.version;
|
|
||||||
logger.debug(`Checking app for updates successful. Update is available (remote: ${updateCheck.restreamer.version}, local: ${packageJson.version})`);
|
|
||||||
}
|
}
|
||||||
logger.info('Checking app for updates successful');
|
|
||||||
this.app.set('updateAvailable', updateAvailable);
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
logger.info('Update check failed', false);
|
return accept(null, false);
|
||||||
}
|
}
|
||||||
}).on('error', () => {
|
|
||||||
logger.info('Update check failed', false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get public ip of the app
|
|
||||||
*/
|
|
||||||
getPublicIp () {
|
|
||||||
logger.info('Getting public ip...', 'start.publicip');
|
|
||||||
exec('public-ip', (err, stdout, stderr) => {
|
|
||||||
if (err) {
|
|
||||||
logger.error(err);
|
|
||||||
}
|
|
||||||
this.app.set('publicIp', stdout.split('\n')[0]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -201,9 +161,10 @@ class RestreamerExpressApp {
|
|||||||
this.app.set('port', process.env.RS_NODE_PORT);
|
this.app.set('port', process.env.RS_NODE_PORT);
|
||||||
server = this.app.listen(this.app.get('port'), ()=> {
|
server = this.app.listen(this.app.get('port'), ()=> {
|
||||||
this.app.set('io', require('socket.io')(server));
|
this.app.set('io', require('socket.io')(server));
|
||||||
|
this.secureSockets();
|
||||||
this.app.set('server', server.address());
|
this.app.set('server', server.address());
|
||||||
|
|
||||||
// promise to determine if the webserver has been started to avoid ws binding before
|
// promise to avoid ws binding before the webserver has been started
|
||||||
this.app.get('websocketsReady').resolve(this.app.get('io'));
|
this.app.get('websocketsReady').resolve(this.app.get('io'));
|
||||||
logger.info(`Webserver running on port ${process.env.RS_NODE_PORT}`);
|
logger.info(`Webserver running on port ${process.env.RS_NODE_PORT}`);
|
||||||
deferred.resolve(server.address().port);
|
deferred.resolve(server.address().port);
|
||||||
@@ -217,7 +178,6 @@ class RestreamerExpressApp {
|
|||||||
*/
|
*/
|
||||||
initAlways () {
|
initAlways () {
|
||||||
this.useSessions();
|
this.useSessions();
|
||||||
this.useAuth();
|
|
||||||
this.addParsers();
|
this.addParsers();
|
||||||
this.addCompression();
|
this.addCompression();
|
||||||
this.addExpressLogger();
|
this.addExpressLogger();
|
||||||
@@ -233,7 +193,7 @@ class RestreamerExpressApp {
|
|||||||
logger.debug('init webserver with PROD environment');
|
logger.debug('init webserver with PROD environment');
|
||||||
this.initAlways();
|
this.initAlways();
|
||||||
this.app.get('/', (req, res)=> {
|
this.app.get('/', (req, res)=> {
|
||||||
res.sendFile(path.join(global.__public, 'index.html'));
|
res.sendFile(path.join(global.__public, 'index.prod.html'));
|
||||||
});
|
});
|
||||||
this.add404ErrorHandling();
|
this.add404ErrorHandling();
|
||||||
this.add500ErrorHandling();
|
this.add500ErrorHandling();
|
||||||
@@ -246,7 +206,7 @@ class RestreamerExpressApp {
|
|||||||
logger.debug('init webserver with DEV environment');
|
logger.debug('init webserver with DEV environment');
|
||||||
this.initAlways();
|
this.initAlways();
|
||||||
this.app.get('/', (req, res)=> {
|
this.app.get('/', (req, res)=> {
|
||||||
res.sendFile(path.join(global.__public, 'index-dev.html'));
|
res.sendFile(path.join(global.__public, 'index.dev.html'));
|
||||||
});
|
});
|
||||||
this.add404ErrorHandling();
|
this.add404ErrorHandling();
|
||||||
this.add500ErrorHandling();
|
this.add500ErrorHandling();
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
/**
|
|
||||||
* @file config for passport local strategy
|
|
||||||
* @link https://github.com/datarhei/restreamer
|
|
||||||
* @copyright 2015 datarhei.org
|
|
||||||
* @license Apache-2.0
|
|
||||||
*/
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
var LocalStrategy = require('passport-local').Strategy;
|
|
||||||
var auth = require(require('path').join(global.__base, 'conf', 'live.json')).auth;
|
|
||||||
|
|
||||||
module.exports = (passport) => {
|
|
||||||
// used to serialize the user for the session
|
|
||||||
passport.serializeUser(function serializeUser (user, done) {
|
|
||||||
done(null, user);
|
|
||||||
});
|
|
||||||
passport.deserializeUser(function deserializeUser (user, done) {
|
|
||||||
done(null, user);
|
|
||||||
});
|
|
||||||
passport.use('local-login', new LocalStrategy(
|
|
||||||
{
|
|
||||||
'usernameField': 'user',
|
|
||||||
'passwordField': 'pass',
|
|
||||||
'passReqToCallback': true // allows us to pass back the entire request to the callback
|
|
||||||
},
|
|
||||||
// callback with user and pass from our form
|
|
||||||
function checkLogin (req, user, pass, done) {
|
|
||||||
var username = process.env.RS_USERNAME || auth.username;
|
|
||||||
var password = process.env.RS_PASSWORD || auth.password;
|
|
||||||
|
|
||||||
// login success
|
|
||||||
if (user === username && pass === password) {
|
|
||||||
// WEBSOCKET SECURITY HERE
|
|
||||||
done(null, auth);
|
|
||||||
} else {
|
|
||||||
done(null, false);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
@@ -8,9 +8,19 @@
|
|||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = new express.Router();
|
const router = new express.Router();
|
||||||
|
const version = require(require('path').join(global.__base, 'package.json')).version;
|
||||||
|
|
||||||
// TODO: solve the circular dependency problem and place Restreamer require here
|
// TODO: solve the circular dependency problem and place Restreamer require here
|
||||||
|
|
||||||
|
router.get('/version', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
'version': version,
|
||||||
|
'update': require.main.require('./classes/Restreamer').data.updateAvailable
|
||||||
|
});
|
||||||
|
});
|
||||||
|
router.get('/ip', (req, res) => {
|
||||||
|
res.end(require.main.require('./classes/Restreamer').data.publicIp);
|
||||||
|
});
|
||||||
router.get('/states', (req, res) => {
|
router.get('/states', (req, res) => {
|
||||||
const states = require.main.require('./classes/Restreamer').data.states;
|
const states = require.main.require('./classes/Restreamer').data.states;
|
||||||
|
|
||||||
|
|||||||
@@ -8,30 +8,33 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
var auth = require(require('path').join(global.__base, 'conf', 'live.json')).auth;
|
||||||
|
|
||||||
module.exports = (app, passport) => {
|
module.exports = (app) => {
|
||||||
// static paths
|
|
||||||
app.get('/favicon.ico', (req, res) => {
|
|
||||||
res.sendFile(path.join(global.__public, 'images', 'favicon.ico'));
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/main.html', (req, res) => {
|
|
||||||
if (req.isAuthenticated()) {
|
|
||||||
res.sendFile(path.join(global.__public, 'main.html'));
|
|
||||||
} else {
|
|
||||||
res.sendFile(path.join(global.__public, 'login.html'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/* Handle Login POST */
|
/* Handle Login POST */
|
||||||
app.post('/login',
|
app.post('/login', (req, res, next) => {
|
||||||
passport.authenticate('local-login', {
|
var username = process.env.RS_USERNAME || auth.username;
|
||||||
'successRedirect': '/',
|
var password = process.env.RS_PASSWORD || auth.password;
|
||||||
'failureRedirect': '/#/login_invalid'
|
var success = false;
|
||||||
})
|
var message = '';
|
||||||
);
|
if (req.body.user === username && req.body.pass === password) {
|
||||||
|
req.session.authenticated = true;
|
||||||
|
success = true;
|
||||||
|
} else {
|
||||||
|
message = 'login_invalid';
|
||||||
|
req.session.destroy();
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
res.json({
|
||||||
|
'success': success,
|
||||||
|
'message': message
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get('/authenticated', (req, res) => {
|
||||||
|
res.json(req.session.authenticated === true);
|
||||||
|
});
|
||||||
app.get('/logout', (req, res) => {
|
app.get('/logout', (req, res) => {
|
||||||
req.logout();
|
req.session.destroy();
|
||||||
res.redirect('/');
|
res.end();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 61 KiB |
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html ng-app="app">
|
<html ng-app="app" ng-controller="appController">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
|
|
||||||
<!-- HEADER MODULE -->
|
<!-- HEADER MODULE -->
|
||||||
<script src="/scripts/Header/HeaderModule.js"></script>
|
<script src="/scripts/Header/HeaderModule.js"></script>
|
||||||
<script src="/scripts/Header/HeaderDirective.js"></script>
|
|
||||||
<script src="/scripts/Header/HeaderController.js"></script>
|
<script src="/scripts/Header/HeaderController.js"></script>
|
||||||
|
<script src="/scripts/Header/HeaderDirective.js"></script>
|
||||||
|
|
||||||
<!-- MAIN MODULE -->
|
<!-- MAIN MODULE -->
|
||||||
<script src="/scripts/Main/MainModule.js"></script>
|
<script src="/scripts/Main/MainModule.js"></script>
|
||||||
@@ -49,8 +49,8 @@
|
|||||||
|
|
||||||
<!-- FOOTER MODULE -->
|
<!-- FOOTER MODULE -->
|
||||||
<script src="/scripts/Footer/FooterModule.js"></script>
|
<script src="/scripts/Footer/FooterModule.js"></script>
|
||||||
<script src="/scripts/Footer/FooterDirective.js"></script>
|
|
||||||
<script src="/scripts/Footer/FooterController.js"></script>
|
<script src="/scripts/Footer/FooterController.js"></script>
|
||||||
|
<script src="/scripts/Footer/FooterDirective.js"></script>
|
||||||
|
|
||||||
<!-- STREAMING INTERFACE MODULE -->
|
<!-- STREAMING INTERFACE MODULE -->
|
||||||
<script src="/scripts/StreamingInterface/StreamingInterfaceModule.js"></script>
|
<script src="/scripts/StreamingInterface/StreamingInterfaceModule.js"></script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html ng-app="app">
|
<html ng-app="app" ng-controller="appController">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<form action='/login' method='post'>
|
|
||||||
<div class="form-group ng-scope">
|
|
||||||
<input id="input_username" class="form-control input ng-pristine ng-untouched ng-valid" type='text' name='user' placeholder='{{"login_username" | translate}}'>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group ng-scope">
|
|
||||||
<input id="input_password" class="form-control input ng-pristine ng-untouched ng-valid" type="password" name='pass' placeholder='{{"login_password" | translate}}'>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-if="login_invalid">{{"login_invalid" | translate}}</div>
|
|
||||||
|
|
||||||
<div class="text-right">
|
|
||||||
<button class="btn btn-success ng-binding ng-scope" type="submit">{{'login_btn' | translate}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
@@ -7,32 +7,31 @@
|
|||||||
<meta name="description" content="Restreamer">
|
<meta name="description" content="Restreamer">
|
||||||
<meta name="author" content="datarhei">
|
<meta name="author" content="datarhei">
|
||||||
<title>Restreamer</title>
|
<title>Restreamer</title>
|
||||||
<link href="/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<script src="/libs/jquery/dist/jquery.min.js"></script>
|
|
||||||
<script src="/libs/bootstrap/dist/js/bootstrap.min.js"></script>
|
|
||||||
<script src="/libs/clappr/dist/clappr.min.js"></script>
|
<script src="/libs/clappr/dist/clappr.min.js"></script>
|
||||||
|
<style>
|
||||||
|
.player-poster[data-poster] .poster-background[data-poster] {
|
||||||
|
height: initial !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin: 0; background-color: #000;">
|
<body>
|
||||||
<div class="container-fluid">
|
<div id="player" style="position:absolute;top:0;right:0;bottom:0;left:0"></div>
|
||||||
<div class="row">
|
<script>
|
||||||
<div>
|
var player = new window.Clappr.Player({
|
||||||
<div class="embed-responsive embed-responsive-16by9">
|
'source': (window.location.protocol === 'https:' ? 'https:' : 'http:') +
|
||||||
<div id="player" class="embed-responsive-item"></div>
|
'//' + window.location.hostname + ':' + window.location.port + '/hls/live.stream.m3u8',
|
||||||
</div>
|
'parentId': '#player',
|
||||||
<script>
|
'baseUrl': '/libs/clappr/dist/',
|
||||||
var player = new window.Clappr.Player({
|
'poster': 'images/live.jpg?t=' + String(new Date().getTime()),
|
||||||
'source': (window.location.protocol === 'https:' ? 'https:' : 'http:') +
|
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
||||||
'//' + window.location.hostname + ':' + window.location.port + '/hls/live.stream.m3u8',
|
'height': '100%',
|
||||||
'parentId': '#player',
|
'width': '100%'
|
||||||
'baseUrl': '/libs/clappr/dist/',
|
});
|
||||||
'poster': 'images/live.jpg',
|
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
|
||||||
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster () {
|
||||||
'height': '100%',
|
posterPlugin.options.poster = 'images/live.jpg?t=' + String(new Date().getTime());
|
||||||
'width': '100%'
|
posterPlugin.render();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,28 +11,30 @@ var app = window.angular.module('app', [
|
|||||||
'pascalprecht.translate',
|
'pascalprecht.translate',
|
||||||
'Footer',
|
'Footer',
|
||||||
'Header',
|
'Header',
|
||||||
|
'Login',
|
||||||
'Main',
|
'Main',
|
||||||
'StreamingInterface',
|
'StreamingInterface'
|
||||||
'Login']);
|
]);
|
||||||
|
|
||||||
|
app.config(($stateProvider) => {
|
||||||
app.config(($stateProvider, $urlRouterProvider) => {
|
|
||||||
$urlRouterProvider.otherwise('/');
|
|
||||||
$stateProvider
|
$stateProvider
|
||||||
.state('main', {
|
.state('login', {
|
||||||
'templateUrl': 'main.html',
|
'controller': 'loginController',
|
||||||
'url': '/:error',
|
'templateUrl': 'views/login.html'
|
||||||
'controller': 'mainController'
|
|
||||||
|
|
||||||
})
|
})
|
||||||
.state('helpSource', {
|
.state('logged-in', {
|
||||||
'templateUrl': 'help/source.html',
|
'controller': 'mainController',
|
||||||
'url': '/help/source'
|
'templateUrl': 'views/main.html'
|
||||||
})
|
|
||||||
.state('helpOptionalOutput', {
|
|
||||||
'templateUrl': 'help/optionalOutput.html',
|
|
||||||
'url': '/help/optionalOutput'
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.controller('appController',
|
||||||
|
['$rootScope', '$state', '$http', ($rootScope, $state, $http) => {
|
||||||
|
$http.get('/authenticated').then((response) => {
|
||||||
|
$rootScope.loggedIn = response.data;
|
||||||
|
});
|
||||||
|
$rootScope.$watch('loggedIn', (value) => {
|
||||||
|
$state.go(value ? 'logged-in' : 'login');
|
||||||
|
});
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|||||||
@@ -5,10 +5,17 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
window.angular.module('Footer').controller('footerController', ['ws', '$scope', 'config', (ws, $scope, config) => {
|
window.angular.module('Footer').controller('footerController',
|
||||||
ws.emit('getVersion');
|
['ws', '$scope', '$http', '$rootScope', 'config', (ws, $scope, $http, $rootScope, config) => {
|
||||||
ws.on('version', (version) => {
|
$http.get('/v1/version').then((response) => {
|
||||||
$scope.version = version;
|
$scope.version = response.data.version;
|
||||||
$scope.config = config;
|
$rootScope.checkForAppUpdatesResult = response.data.update;
|
||||||
});
|
$scope.config = config;
|
||||||
}]);
|
});
|
||||||
|
$scope.logout = () => {
|
||||||
|
$http.get('/logout').then(() => {
|
||||||
|
$rootScope.loggedIn = false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ window.angular.module('Footer').directive('footer', () => {
|
|||||||
return {
|
return {
|
||||||
'restrict': 'A',
|
'restrict': 'A',
|
||||||
'replace': true,
|
'replace': true,
|
||||||
'templateUrl': '/scripts/Footer/_footer.html',
|
'templateUrl': '/views/footer.html',
|
||||||
'controller': 'footerController'
|
'controller': 'footerController'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,19 +5,21 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
window.angular.module('Header').controller('headerController', ['$scope', '$translate', 'loggerService', ($scope, $translate, loggerService) => {
|
window.angular.module('Header').controller('headerController',
|
||||||
$scope.currentLocale = $translate.preferredLanguage();
|
['$scope', '$translate', 'loggerService', ($scope, $translate, loggerService) => {
|
||||||
$scope.switchLanguage = (locale) => {
|
$scope.currentLocale = $translate.preferredLanguage();
|
||||||
$scope.currentLocale = locale;
|
$scope.switchLanguage = (locale) => {
|
||||||
$translate.use(locale).then(
|
$scope.currentLocale = locale;
|
||||||
() => {
|
$translate.use(locale).then(
|
||||||
loggerService.info('Switched language to ' + locale);
|
() => {
|
||||||
},
|
loggerService.info('Switched language to ' + locale);
|
||||||
(error) => {
|
},
|
||||||
loggerService.error('INFO', 'Switching language to ' + locale + ' failed: ' + error);
|
(error) => {
|
||||||
});
|
loggerService.error('INFO', 'Switching language to ' + locale + ' failed: ' + error);
|
||||||
};
|
});
|
||||||
$scope.langIs = function langIs (locale) {
|
};
|
||||||
return locale === $scope.currentLocale;
|
$scope.langIs = (locale) => {
|
||||||
};
|
return locale === $scope.currentLocale;
|
||||||
}]);
|
};
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ window.angular.module('Header').directive('header', () => {
|
|||||||
return {
|
return {
|
||||||
'restrict': 'A',
|
'restrict': 'A',
|
||||||
'replace': true,
|
'replace': true,
|
||||||
'templateUrl': '/scripts/Header/_header.html',
|
'templateUrl': '/views/header.html',
|
||||||
'controller': 'headerController'
|
'controller': 'headerController'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,4 +5,13 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
window.angular.module('Login').controller('loginController', [], () => {});
|
window.angular.module('Login').controller('loginController',
|
||||||
|
['$scope', '$http', '$rootScope', function loginController ($scope, $http, $rootScope) {
|
||||||
|
$scope.submit = function submit () {
|
||||||
|
$http.post('/login', {'user': $scope.user, 'pass': $scope.pass}).then((response) => {
|
||||||
|
$scope.message = response.data.message;
|
||||||
|
$rootScope.loggedIn = response.data.success;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
|||||||
@@ -5,5 +5,4 @@
|
|||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
// create new angular module
|
|
||||||
window.angular.module('Login', []);
|
window.angular.module('Login', []);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @file holds the Angularjs mainController
|
* @file holds the AngularJS mainController
|
||||||
* @link https://github.com/datarhei/restreamer
|
* @link https://github.com/datarhei/restreamer
|
||||||
* @copyright 2015 datarhei.org
|
* @copyright 2015 datarhei.org
|
||||||
* @license Apache-2.0
|
* @license Apache-2.0
|
||||||
@@ -7,151 +7,153 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
window.angular.module('Main').controller('mainController',
|
window.angular.module('Main').controller('mainController',
|
||||||
['ws', '$scope', '$location', '$rootScope', '$stateParams', 'config',
|
['ws', '$scope', '$location', '$rootScope', '$stateParams', 'config', function mainController (ws, $scope, $location, $rootScope, $stateParams, config) {
|
||||||
function mainController (ws, $scope, $location, $rootScope, $stateParams, config) {
|
let setup = false;
|
||||||
let setup = false;
|
let player = null;
|
||||||
let player = null;
|
let posterPlugin = null;
|
||||||
|
|
||||||
if ($stateParams.error === 'login_invalid') {
|
$scope.config = config;
|
||||||
$scope.login_invalid = 'login_invalid';
|
|
||||||
|
const updateSnapshot = () => {
|
||||||
|
if (posterPlugin !== null) {
|
||||||
|
posterPlugin.options.poster = 'images/live.jpg?t=' + String(new Date().getTime());
|
||||||
|
if (!player.isPlaying()) {
|
||||||
|
posterPlugin.render();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$scope.config = config;
|
const initClappr = () => {
|
||||||
|
player = new window.Clappr.Player({
|
||||||
|
'source': (window.location.protocol === 'https:' ? 'https:' : 'http:') +
|
||||||
|
'//' + window.location.hostname + ':' + window.location.port + '/hls/live.stream.m3u8',
|
||||||
|
'parentId': '#player',
|
||||||
|
'baseUrl': '/libs/clappr/dist/',
|
||||||
|
'poster': 'images/live.jpg?t=' + String(new Date().getTime()),
|
||||||
|
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
||||||
|
'height': '100%',
|
||||||
|
'width': '100%'
|
||||||
|
});
|
||||||
|
posterPlugin = player.core.mediaControl.container.getPlugin('poster');
|
||||||
|
player.on(window.Clappr.Events.PLAYER_STOP, () => {
|
||||||
|
posterPlugin.render();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const initClappr = () => {
|
$scope.optionalOutputInputInvalid = false;
|
||||||
player = new window.Clappr.Player({
|
$scope.nginxRepeatStreamInputInvalid = false;
|
||||||
'source': (window.location.protocol === 'https:' ? 'https:' : 'http:') +
|
|
||||||
'//' + window.location.hostname + ':' + window.location.port + '/hls/live.stream.m3u8',
|
|
||||||
'parentId': '#player',
|
|
||||||
'baseUrl': '/libs/clappr/dist/',
|
|
||||||
'poster': 'images/live.jpg',
|
|
||||||
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
|
||||||
'height': '100%',
|
|
||||||
'width': '100%'
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.loggedIn = false;
|
$scope.reStreamerData = {
|
||||||
|
'retryCounter': {
|
||||||
$scope.optionalOutputInputInvalid = false;
|
'repeatToLocalNginx': 0,
|
||||||
$scope.nginxRepeatStreamInputInvalid = false;
|
'repeatToOptionalOutput': 0
|
||||||
|
},
|
||||||
$scope.reStreamerData = {
|
'options': {
|
||||||
'retryCounter': {
|
'rtspTcp': false
|
||||||
'repeatToLocalNginx': 0,
|
},
|
||||||
'repeatToOptionalOutput': 0
|
'states': {
|
||||||
|
'repeatToLocalNginx': {
|
||||||
|
'type': ''
|
||||||
},
|
},
|
||||||
'options': {
|
'repeatToOptionalOutput': {
|
||||||
'rtspTcp': false
|
'type': ''
|
||||||
},
|
|
||||||
'states': {
|
|
||||||
'repeatToLocalNginx': {
|
|
||||||
'type': ''
|
|
||||||
},
|
|
||||||
'repeatToOptionalOutput': {
|
|
||||||
'type': ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
'userActions': {
|
|
||||||
'repeatToLocalNginx': '',
|
|
||||||
'repeatToOptionalOutput': ''
|
|
||||||
},
|
|
||||||
'progresses': {
|
|
||||||
'repeatToLocalNginx': '',
|
|
||||||
'repeatToOptionalOutput': ''
|
|
||||||
},
|
|
||||||
'addresses': {
|
|
||||||
'optionalOutputAddress': '',
|
|
||||||
'srcAddress': ''
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
'userActions': {
|
||||||
|
'repeatToLocalNginx': '',
|
||||||
|
'repeatToOptionalOutput': ''
|
||||||
|
},
|
||||||
|
'progresses': {
|
||||||
|
'repeatToLocalNginx': '',
|
||||||
|
'repeatToOptionalOutput': ''
|
||||||
|
},
|
||||||
|
'addresses': {
|
||||||
|
'optionalOutputAddress': '',
|
||||||
|
'srcAddress': ''
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
$rootScope.windowLocationPort = $location.port();
|
$rootScope.windowLocationPort = $location.port();
|
||||||
|
|
||||||
$scope.optionalOutput = '';
|
$scope.optionalOutput = '';
|
||||||
|
|
||||||
$scope.showStopButton = (streamType) => {
|
$scope.showStopButton = (streamType) => {
|
||||||
return $scope.reStreamerData.userActions[streamType] === 'start';
|
return $scope.reStreamerData.userActions[streamType] === 'start';
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.showStartButton = (streamType) => {
|
$scope.showStartButton = (streamType) => {
|
||||||
return $scope.reStreamerData.userActions[streamType] === 'stop';
|
return $scope.reStreamerData.userActions[streamType] === 'stop';
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.openPlayer = () => {
|
$scope.openPlayer = () => {
|
||||||
if (player === null) {
|
if (player === null) {
|
||||||
initClappr();
|
initClappr();
|
||||||
}
|
}
|
||||||
$('#player-modal').modal('show').on('hide.bs.modal', function closeModal (e) {
|
$('#player-modal').modal('show').on('hide.bs.modal', function closeModal (e) {
|
||||||
player.stop();
|
player.stop();
|
||||||
$(this).off('hide.bs.modal');
|
$(this).off('hide.bs.modal');
|
||||||
$(this).modal('hide');
|
$(this).modal('hide');
|
||||||
return e.preventDefault();
|
return e.preventDefault();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure Websockets
|
||||||
|
*/
|
||||||
|
|
||||||
|
ws.emit('checkStates'); // check states of hls and rtmp stream
|
||||||
|
|
||||||
|
// prohibit double binding of events
|
||||||
|
if (!setup) {
|
||||||
/*
|
/*
|
||||||
* Configure Websockets
|
* test websockets connection (should print below message to browser console if it works)
|
||||||
*/
|
*/
|
||||||
|
ws.on('updateProgress', (progresses) => {
|
||||||
|
$scope.reStreamerData.progresses = progresses;
|
||||||
|
});
|
||||||
|
ws.on('publicIp', (publicIp) => {
|
||||||
|
$rootScope.publicIp = publicIp;
|
||||||
|
});
|
||||||
|
ws.on('updateStreamData', (reStreamerData) => {
|
||||||
|
$scope.reStreamerData = reStreamerData;
|
||||||
|
if ($scope.showStopButton('repeatToOptionalOutput')) {
|
||||||
|
// checkbox
|
||||||
|
$scope.activateOptionalOutput = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ws.on('snapshot', updateSnapshot);
|
||||||
|
}
|
||||||
|
|
||||||
// check states of hls and rtmp stream
|
$scope.startStream = (streamType) => {
|
||||||
ws.emit('checkStates');
|
const rtmpRegex = /^(?:rtmp:\/\/|rtsp:\/\/)(?:(?:[^:])+:(?:[^@])+@)?(?:(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}))(:?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?(?:\/.*)?/;
|
||||||
|
var optionalOutput = '';
|
||||||
|
|
||||||
// check for app updates
|
if ($scope.activateOptionalOutput === true) {
|
||||||
ws.emit('checkForAppUpdates');
|
optionalOutput = $scope.reStreamerData.addresses.optionalOutputAddress;
|
||||||
|
|
||||||
// prohibit double binding of events
|
|
||||||
if (!setup) {
|
|
||||||
/*
|
|
||||||
* test websockets connection (should print below message to browser console if it works)
|
|
||||||
*/
|
|
||||||
ws.on('updateProgress', (progresses) => {
|
|
||||||
$scope.reStreamerData.progresses = progresses;
|
|
||||||
});
|
|
||||||
ws.on('publicIp', (publicIp) => {
|
|
||||||
$rootScope.publicIp = publicIp;
|
|
||||||
});
|
|
||||||
ws.on('updateStreamData', (reStreamerData) => {
|
|
||||||
$scope.reStreamerData = reStreamerData;
|
|
||||||
if ($scope.showStopButton('repeatToOptionalOutput')) {
|
|
||||||
// checkbox
|
|
||||||
$scope.activateOptionalOutput = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ws.on('checkForAppUpdatesResult', (result) => {
|
|
||||||
$rootScope.checkForAppUpdatesResult = result;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.startStream = (streamType) => {
|
if (streamType === 'repeatToOptionalOutput') {
|
||||||
const rtmpRegex = /^(?:rtmp:\/\/|rtsp:\/\/)(?:(?:[^:])+:(?:[^@])+@)?(?:(?:(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))|(?:(?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63}))(:?:[0-9]{1,4}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])?(?:\/.*)?/;
|
$scope.optionalOutputInputInvalid = !rtmpRegex.test(optionalOutput);
|
||||||
var optionalOutput = '';
|
if ($scope.optionalOutputInputInvalid) {
|
||||||
|
return;
|
||||||
if ($scope.activateOptionalOutput === true) {
|
|
||||||
optionalOutput = $scope.reStreamerData.addresses.optionalOutputAddress;
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
if (streamType === 'repeatToOptionalOutput') {
|
$scope.nginxRepeatStreamInputInvalid = !rtmpRegex.test($scope.reStreamerData.addresses.srcAddress);
|
||||||
$scope.optionalOutputInputInvalid = !rtmpRegex.test(optionalOutput);
|
if ($scope.nginxRepeatStreamInputInvalid) {
|
||||||
if ($scope.optionalOutputInputInvalid) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$scope.nginxRepeatStreamInputInvalid = !rtmpRegex.test($scope.reStreamerData.addresses.srcAddress);
|
|
||||||
if ($scope.nginxRepeatStreamInputInvalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ws.emit('startStream', {
|
ws.emit('startStream', {
|
||||||
'src': $scope.reStreamerData.addresses.srcAddress,
|
'src': $scope.reStreamerData.addresses.srcAddress,
|
||||||
'options': $scope.reStreamerData.options,
|
'options': $scope.reStreamerData.options,
|
||||||
'streamType': streamType,
|
'streamType': streamType,
|
||||||
'optionalOutput': optionalOutput
|
'optionalOutput': optionalOutput
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.stopStream = (streamType) => {
|
$scope.stopStream = (streamType) => {
|
||||||
ws.emit('stopStream', streamType);
|
ws.emit('stopStream', streamType);
|
||||||
};
|
};
|
||||||
}]);
|
}]
|
||||||
|
);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
// styles of the logging output
|
// styles of the logging output
|
||||||
const INFO = 'color: #0000FF; font-weight: bold';
|
const INFO = 'color: #0000FF; font-weight: bold';
|
||||||
const DEBUG = 'color: #AABBCC; font-weight: bold';
|
const DEBUG = 'color: #AABBCC; font-weight: bold';
|
||||||
const ERROR = 'color: #FF0011d; font-weight: bold';
|
const ERROR = 'color: #FF0011; font-weight: bold';
|
||||||
const WEBSOCKETS_IN = 'color: #00BFFF; font-weight: bold';
|
const WEBSOCKETS_IN = 'color: #00BFFF; font-weight: bold';
|
||||||
const WEBSOCKETS_OUT = 'color: #00BF00; font-weight: bold';
|
const WEBSOCKETS_OUT = 'color: #00BF00; font-weight: bold';
|
||||||
const WEBSOCKETS_NAMESPACE = 'color: #00BF00; font-weight: bold';
|
const WEBSOCKETS_NAMESPACE = 'color: #00BF00; font-weight: bold';
|
||||||
@@ -71,7 +71,7 @@ const LoggerService = function loggerService () {
|
|||||||
* @param {string} type
|
* @param {string} type
|
||||||
*/
|
*/
|
||||||
this.log = (style, message, type) => {
|
this.log = (style, message, type) => {
|
||||||
console.log('%c [' + type + ']' + message, style);
|
console.log('%c [' + type + '] ' + message, style);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,22 +8,32 @@
|
|||||||
/* eslint no-undef: 0*/
|
/* eslint no-undef: 0*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const WebsocketsService = function websockeService ($rootScope, loggerService) {
|
const WebsocketsService = function websocketsService ($rootScope, loggerService) {
|
||||||
this.$rootScope = $rootScope;
|
this.$rootScope = $rootScope;
|
||||||
this.loggerService = loggerService;
|
this.loggerService = loggerService;
|
||||||
this.socket = io.connect();
|
this.socket = null;
|
||||||
this.loggerService.websocketsNamespace('websockets connected');
|
|
||||||
|
|
||||||
|
$rootScope.$watch('loggedIn', (loggedIn) => {
|
||||||
|
if (loggedIn) {
|
||||||
|
this.socket = io.connect();
|
||||||
|
this.loggerService.websocketsNamespace('WS connected');
|
||||||
|
} else if (this.socket !== null) {
|
||||||
|
this.socket.disconnect();
|
||||||
|
this.loggerService.websocketsNamespace('WS disconnected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* emit an event to socket
|
* emit an event to socket
|
||||||
* @param event
|
* @param event
|
||||||
* @param data
|
* @param data
|
||||||
* @returns {WebsocketsService}
|
* @returns {websocketsService}
|
||||||
*/
|
*/
|
||||||
this.emit = (event, data) => {
|
this.emit = (event, data) => {
|
||||||
this.loggerService.websocketsOut(`emit event "${event}"`);
|
if (this.socket) {
|
||||||
this.socket.emit(event, data);
|
this.loggerService.websocketsOut(`emit event "${event}"`);
|
||||||
|
this.socket.emit(event, data);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -31,17 +41,19 @@ const WebsocketsService = function websockeService ($rootScope, loggerService) {
|
|||||||
* react on an event to socket with callback
|
* react on an event to socket with callback
|
||||||
* @param event
|
* @param event
|
||||||
* @param {function} callback
|
* @param {function} callback
|
||||||
* @returns {WebsocketsService}
|
* @returns {websocketsService}
|
||||||
*/
|
*/
|
||||||
this.on = (event, callback) => {
|
this.on = (event, callback) => {
|
||||||
var self = this;
|
var self = this;
|
||||||
this.loggerService.websocketsIn(`got event "${event}"`);
|
if (this.socket) {
|
||||||
this.socket.on(event, function woEvent () {
|
this.loggerService.websocketsIn(`got event "${event}"`);
|
||||||
var args = arguments;
|
this.socket.on(event, function woEvent () {
|
||||||
self.$rootScope.$apply(function weApply () {
|
var args = arguments;
|
||||||
callback.apply(null, args);
|
self.$rootScope.$apply(function weApply () {
|
||||||
|
callback.apply(null, args);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
return this;
|
return this;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -51,7 +63,9 @@ const WebsocketsService = function websockeService ($rootScope, loggerService) {
|
|||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
this.off = (event, callback) => {
|
this.off = (event, callback) => {
|
||||||
this.socket.removeListener(event, callback);
|
if (this.socket) {
|
||||||
|
this.socket.removeListener(event, callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
/**
|
/**
|
||||||
* Streaming Status Controller
|
* Streaming Status Controller
|
||||||
*
|
*
|
||||||
* controlls the display of the streaming status (fps, status)
|
* controls the display of the streaming status (fps, status)
|
||||||
*/
|
*/
|
||||||
window.angular.module('StreamingInterface').controller('streamingStatusController',
|
window.angular.module('StreamingInterface').controller('streamingStatusController',
|
||||||
['$scope', ($scope) => {
|
['$scope', ($scope) => {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ window.angular.module('StreamingInterface').directive('streamingStatus', () => {
|
|||||||
},
|
},
|
||||||
'restrict': 'E',
|
'restrict': 'E',
|
||||||
'replace': true,
|
'replace': true,
|
||||||
'templateUrl': '/scripts/StreamingInterface/_streamingStatus.html',
|
'templateUrl': '/views/status.html',
|
||||||
'controller': 'streamingStatusController'
|
'controller': 'streamingStatusController'
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,12 @@
|
|||||||
v{{version}}
|
v{{version}}
|
||||||
</span>
|
</span>
|
||||||
<a class="btn btn-xs btn-success ng-binding ng-scope"
|
<a class="btn btn-xs btn-success ng-binding ng-scope"
|
||||||
ng-if="checkForAppUpdatesResult != false" href="{{config.urls.updatePage}}" target="_blank">
|
ng-if="checkForAppUpdatesResult === true" href="{{config.urls.updatePage}}" target="_blank">
|
||||||
{{'update_btn' | translate}}
|
{{'update_btn' | translate}}
|
||||||
</a>
|
</a>
|
||||||
<p class="pull-right links">
|
<p class="pull-right links">
|
||||||
<a href="{{config.urls.issueTracker}}" target="_blank">{{'issue_tracker' | translate}}</a>
|
<a href="{{config.urls.issueTracker}}" target="_blank">{{'issue_tracker' | translate}}</a>
|
||||||
<a href="{{config.urls.projectPage}}" target="_blank">{{'project_page' | translate}}</a>
|
<a href="{{config.urls.projectPage}}" target="_blank">{{'project_page' | translate}}</a>
|
||||||
<a ng-if="loggedIn" href="/logout">{{'logout' | translate}}</a>
|
<a ng-if="loggedIn" ng-click="logout()">{{'logout' | translate}}</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
22
src/webserver/public/views/login.html
Normal file
22
src/webserver/public/views/login.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<form ng-submit="submit()">
|
||||||
|
<div class="form-group ng-scope">
|
||||||
|
<input id="input_username"
|
||||||
|
class="form-control input ng-pristine ng-untouched ng-valid"
|
||||||
|
type="text"
|
||||||
|
name="user"
|
||||||
|
ng-model="user"
|
||||||
|
placeholder="{{'login_username' | translate}}">
|
||||||
|
</div>
|
||||||
|
<div class="form-group ng-scope">
|
||||||
|
<input id="input_password"
|
||||||
|
class="form-control input ng-pristine ng-untouched ng-valid"
|
||||||
|
type="password"
|
||||||
|
name="pass"
|
||||||
|
ng-model="pass"
|
||||||
|
placeholder="{{'login_password' | translate}}">
|
||||||
|
</div>
|
||||||
|
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-if="message">{{message | translate}}</div>
|
||||||
|
<div class="text-right">
|
||||||
|
<button class="btn btn-success ng-binding ng-scope" type="submit">{{'login_btn' | translate}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<!--
|
<!--
|
||||||
Repeat to local nginx
|
Repeat to local nginx
|
||||||
-->
|
-->
|
||||||
<meta ng-init="$root.loggedIn = true">
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>
|
<label>
|
||||||
{{'input_title' | translate}}
|
{{'input_title' | translate}}
|
||||||
Reference in New Issue
Block a user