mirror of
https://github.com/datarhei/restreamer.git
synced 2025-12-12 06:24:08 +01:00
ADD 0.1.0-RC6
This commit is contained in:
2
.bowerrc
2
.bowerrc
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"directory": "bin/webserver/public/libs"
|
||||
"directory": "src/webserver/public/libs"
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ docs
|
||||
node_modules
|
||||
bin
|
||||
db
|
||||
src/webserver/public/lib/*
|
||||
src/webserver/public/libs
|
||||
checkDeployment.sh
|
||||
|
||||
@@ -10,6 +10,7 @@ indent_size = 4
|
||||
indent_style = space
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
spaces_around_brackets = outside
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
gruntfile.js
|
||||
node_modules
|
||||
src/webserver/public/dist
|
||||
src/webserver/public/libs
|
||||
*.min.js
|
||||
|
||||
203
.eslintrc.json
203
.eslintrc.json
@@ -26,10 +26,16 @@
|
||||
"rules": {
|
||||
"accessor-pairs": 2,
|
||||
"block-scoped-var": 2,
|
||||
"complexity": [0, 11],
|
||||
"complexity": [
|
||||
0,
|
||||
11
|
||||
],
|
||||
"curly": 2,
|
||||
"default-case": 2,
|
||||
"dot-location": [2, "property"],
|
||||
"dot-location": [
|
||||
2,
|
||||
"property"
|
||||
],
|
||||
"dot-notation": 2,
|
||||
"eqeqeq": 2,
|
||||
"no-alert": 2,
|
||||
@@ -37,7 +43,6 @@
|
||||
"no-case-declarations": 2,
|
||||
"no-div-regex": 2,
|
||||
"no-else-return": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-eq-null": 2,
|
||||
"no-eval": 2,
|
||||
"no-extend-native": 2,
|
||||
@@ -59,7 +64,7 @@
|
||||
"no-octal": 2,
|
||||
"no-octal-escape": 2,
|
||||
"no-param-reassign": 2,
|
||||
"no-process-env": 2,
|
||||
"no-process-env": 0,
|
||||
"no-proto": 2,
|
||||
"no-redeclare": 2,
|
||||
"no-return-assign": 2,
|
||||
@@ -72,13 +77,25 @@
|
||||
"no-useless-call": 2,
|
||||
"no-useless-concat": 2,
|
||||
"no-void": 2,
|
||||
"no-warning-comments": [0, {"terms": ["todo", "fixme"], "location": "start"}],
|
||||
"no-warning-comments": [
|
||||
0,
|
||||
{
|
||||
"terms": [
|
||||
"todo",
|
||||
"fixme"
|
||||
],
|
||||
"location": "start"
|
||||
}
|
||||
],
|
||||
"no-with": 2,
|
||||
"radix": 2,
|
||||
"vars-on-top": 2,
|
||||
"wrap-iife": 2,
|
||||
"yoda": 2,
|
||||
"init-declarations": [2, "always"],
|
||||
"init-declarations": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"no-catch-shadow": 2,
|
||||
"no-delete-var": 2,
|
||||
"no-label-var": 2,
|
||||
@@ -87,84 +104,188 @@
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 2,
|
||||
"no-unused-vars": 2,
|
||||
"no-use-before-define": 2,
|
||||
"no-unused-vars": 1,
|
||||
"callback-return": 2,
|
||||
"global-require": 2,
|
||||
"global-require": 0,
|
||||
"handle-callback-err": 2,
|
||||
"no-mixed-requires": 2,
|
||||
"no-new-require": 2,
|
||||
"no-path-concat": 2,
|
||||
"no-process-exit": 2,
|
||||
"array-bracket-spacing": [2, "always"],
|
||||
"block-spacing": [2, "always"],
|
||||
"no-process-exit": 0,
|
||||
"array-bracket-spacing": [
|
||||
0,
|
||||
"never"
|
||||
],
|
||||
"block-spacing": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"brace-style": 2,
|
||||
"camelcase": 2,
|
||||
"comma-spacing": [2, {"before": false, "after": true}],
|
||||
"comma-style": [2, "last"],
|
||||
"computed-property-spacing": [2, "never"],
|
||||
"consistent-this": [2, "that"],
|
||||
"comma-spacing": [
|
||||
2,
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"comma-style": [
|
||||
2,
|
||||
"last"
|
||||
],
|
||||
"computed-property-spacing": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"consistent-this": [
|
||||
2,
|
||||
"self"
|
||||
],
|
||||
"eol-last": 2,
|
||||
"func-names": 2,
|
||||
"indent": [2,4],
|
||||
"key-spacing": [2, {"beforeColon": false, "afterColon": true}],
|
||||
"indent": [
|
||||
2,
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"key-spacing": [
|
||||
2,
|
||||
{
|
||||
"beforeColon": false,
|
||||
"afterColon": true
|
||||
}
|
||||
],
|
||||
"keyword-spacing": 2,
|
||||
"linebreak-style": 2,
|
||||
"lines-around-comment": [2, { "beforeBlockComment": true, "beforeLineComment": true }],
|
||||
"max-depth": [2, 5],
|
||||
"max-len": [2, 80, 4, {"ignoreUrls": true}],
|
||||
"max-nested-callbacks": [2, 3],
|
||||
"max-params": [2, 4],
|
||||
"max-statements": [2, 10],
|
||||
"lines-around-comment": [
|
||||
0,
|
||||
{
|
||||
"beforeBlockComment": false,
|
||||
"beforeLineComment": false
|
||||
}
|
||||
],
|
||||
"max-depth": [
|
||||
2,
|
||||
5
|
||||
],
|
||||
"max-len": [
|
||||
2,
|
||||
160,
|
||||
4,
|
||||
{
|
||||
"ignoreUrls": true,
|
||||
"ignorePattern": "(/.*/|`.*`)"
|
||||
}
|
||||
],
|
||||
"max-nested-callbacks": [
|
||||
2,
|
||||
3
|
||||
],
|
||||
"max-params": [
|
||||
2,
|
||||
8
|
||||
],
|
||||
"max-statements": [
|
||||
2,
|
||||
40
|
||||
],
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 2,
|
||||
"newline-after-var": 0,
|
||||
"no-array-constructor": 2,
|
||||
"no-bitwise": 2,
|
||||
"no-continue": 2,
|
||||
"no-inline-comments": 2,
|
||||
"no-inline-comments": 0,
|
||||
"no-lonely-if": 2,
|
||||
"no-mixed-spaces-and-tabs": 2,
|
||||
"no-multiple-empty-lines": [0, {"max": 2}],
|
||||
"no-multiple-empty-lines": [
|
||||
0,
|
||||
{
|
||||
"max": 2
|
||||
}
|
||||
],
|
||||
"no-negated-condition": 2,
|
||||
"no-nested-ternary": 2,
|
||||
"no-new-object": 2,
|
||||
"no-plusplus": 2,
|
||||
"no-plusplus": 0,
|
||||
"no-restricted-syntax": 2,
|
||||
"no-whitespace-before-property": 2,
|
||||
"no-spaced-func": 2,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-unneeded-ternary": 2,
|
||||
"object-curly-spacing": [2, "never"],
|
||||
"one-var": [2, "never"],
|
||||
"operator-assignment": [2, "never"],
|
||||
"operator-linebreak": [2, "after"],
|
||||
"padded-blocks": [2, "never"],
|
||||
"quote-props": [2, "always"],
|
||||
"quotes": [2, "single"],
|
||||
"require-jsdoc": [2, {
|
||||
"object-curly-spacing": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"one-var": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"operator-assignment": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"operator-linebreak": [
|
||||
2,
|
||||
"after"
|
||||
],
|
||||
"padded-blocks": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"quote-props": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"quotes": [
|
||||
2,
|
||||
"single"
|
||||
],
|
||||
"require-jsdoc": [
|
||||
2,
|
||||
{
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
"MethodDefinition": false,
|
||||
"ClassDeclaration": false
|
||||
}
|
||||
}],
|
||||
}
|
||||
],
|
||||
"semi-spacing": 2,
|
||||
"semi": [2, "always"],
|
||||
"semi": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"sort-vars": 2,
|
||||
"sort-imports": 2,
|
||||
"space-before-blocks": 2,
|
||||
"space-before-function-paren": 2,
|
||||
"space-in-parens": [2, "never"],
|
||||
"space-in-parens": [
|
||||
2,
|
||||
"never"
|
||||
],
|
||||
"space-infix-ops": 2,
|
||||
"space-unary-ops": 2,
|
||||
"spaced-comment": [2, "always"],
|
||||
"spaced-comment": [
|
||||
2,
|
||||
"always"
|
||||
],
|
||||
"wrap-regex": 2
|
||||
},
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true,
|
||||
"browser": true
|
||||
"browser": true,
|
||||
"jquery": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true,
|
||||
"experimentalObjectRestSpread": true
|
||||
}
|
||||
},
|
||||
"extends": "eslint:recommended"
|
||||
}
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -63,6 +63,10 @@ bin
|
||||
# Project files #
|
||||
#################
|
||||
static/webserver/public/libs/**
|
||||
src/webserver/public/libs/**
|
||||
src/webserver/public/dist/
|
||||
db/**
|
||||
heapdump
|
||||
*.map
|
||||
*.min.js
|
||||
*.min.css
|
||||
|
||||
26
CHANGELOG.md
26
CHANGELOG.md
@@ -1,3 +1,29 @@
|
||||
## Changes from 0.1.0-RC5 to 0.1.0-RC6
|
||||
|
||||
* updated NPM/Bower packages
|
||||
* updated FFmpeg to 2.8.6
|
||||
* switched to a NGINX-RTMP fork of [Sergey Dryabzhinsky](https://github.com/sergey-dryabzhinsky/nginx-rtmp-module)
|
||||
* added ECMA6 development mode (RS_NODE_ENV=dev) and updated NodeJS to 5.7
|
||||
* refactored frontend structure
|
||||
* finished ECMA6 frontend remodeling
|
||||
* started backend refactoring
|
||||
* optimized fake audio process (resolved NGINX error "hls: force fragment split")
|
||||
* added FFmpeg patch of [Andrew Shulgin](https://github.com/andrew-shulgin) (Ignore invalid sprop-parameter-sets missing PPS)
|
||||
* renamed environment variables (old environment variables are still supported but will be deprecated in the future)
|
||||
* RS_NODE_PORT
|
||||
* RS_NODE_ENV
|
||||
* RS_LOGGER_LEVEL
|
||||
* RS_TIMEZONE
|
||||
* RS_SNAPSHOT_REFRESH_INTERVAL
|
||||
* RS_CREATE_HEAPDUMPS
|
||||
* RS_USERNAME
|
||||
* RS_PASSWORD
|
||||
* several small bugfixes and improvements
|
||||
|
||||
#### Team enlargement
|
||||
|
||||
* [Andrew Shulgin](https://github.com/andrew-shulgin) - Many thanks for your support and welcome to our team!
|
||||
|
||||
## Changes from 0.1.0-RC4.1 to 0.1.0-RC5
|
||||
|
||||
* updated NPM packages, NGINX to 1.9.9 and FFmpeg to 2.8.5
|
||||
|
||||
137
Dockerfile
137
Dockerfile
@@ -1,101 +1,130 @@
|
||||
FROM node:4.2.6-slim
|
||||
FROM node:5.7.1-slim
|
||||
|
||||
MAINTAINER datarhei <info@datarhei.org>
|
||||
|
||||
ENV FFMPEG_VERSION 2.8.5
|
||||
ENV FFMPEG_VERSION 2.8.6
|
||||
ENV YASM_VERSION 1.3.0
|
||||
ENV LAME_VERSION 3_99_5
|
||||
ENV NGINX_VERSION 1.9.9
|
||||
ENV NGINX_RTMP_VERSION 1.1.7
|
||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
||||
|
||||
ENV SRC /usr/local
|
||||
ENV LD_LIBRARY_PATH ${SRC}/lib
|
||||
ENV PKG_CONFIG_PATH ${SRC}/lib/pkgconfig
|
||||
ENV SRC "/usr/local"
|
||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
||||
|
||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake perl build-essential libpcre3-dev"
|
||||
ENV 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 wget git libpcre3 tar ${BUILDDEPS}
|
||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
||||
|
||||
# yasm
|
||||
RUN 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} && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# x264
|
||||
RUN DIR=$(mktemp -d) && cd ${DIR} && \
|
||||
git clone --depth 1 git://git.videolan.org/x264 && \
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||
cd x264 && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" --enable-static && \
|
||||
make && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-static \
|
||||
--disable-cli && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# libmp3lame
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --bindir="${SRC}/bin" --disable-shared --enable-nasm && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-nasm \
|
||||
--disable-shared && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean&& \
|
||||
rm -rf ${DIR}
|
||||
make distclean && \
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# ffmpeg
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --extra-cflags="-I${SRC}/include" --extra-ldflags="-L${SRC}/lib" --bindir="${SRC}/bin" \
|
||||
--extra-libs=-ldl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-gpl \
|
||||
--enable-postproc --enable-nonfree --enable-avresample --disable-debug --enable-small --enable-openssl \
|
||||
--disable-doc --disable-ffserver && \
|
||||
make && \
|
||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
curl -Lks "https://github.com/FFmpeg/FFmpeg/commit/1c7e2cf9d33968375ee4025d2279c937e147dae2.patch" | patch -p1 && \
|
||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||
./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}
|
||||
RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/libc.conf
|
||||
cp qt-faststart "${SRC}/bin" && \
|
||||
rm -rf "${DIR}"
|
||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
||||
RUN ffmpeg -buildconf
|
||||
|
||||
# nginx-rtmp
|
||||
RUN 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/arut/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 && \
|
||||
RUN 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}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||
apt-get install -y --force-yes git && \
|
||||
rm -rf /tmp/*
|
||||
|
||||
COPY . /restreamer
|
||||
WORKDIR /restreamer
|
||||
|
||||
RUN npm install -g bower grunt-cli public-ip eslint@v2.0.0-beta.3 && \
|
||||
RUN npm install -g bower grunt grunt-cli nodemon public-ip eslint && \
|
||||
npm install && \
|
||||
grunt build && \
|
||||
npm prune --production
|
||||
npm prune --production && \
|
||||
npm cache clean && \
|
||||
bower cache clean --allow-root
|
||||
|
||||
ENV RESTREAMER_USERNAME admin
|
||||
ENV RESTREAMER_PASSWORD datarhei
|
||||
ENV RS_USERNAME admin
|
||||
ENV RS_PASSWORD datarhei
|
||||
|
||||
EXPOSE 8080
|
||||
VOLUME ["/restreamer/db"]
|
||||
|
||||
@@ -2,115 +2,144 @@ FROM resin/rpi-raspbian:jessie
|
||||
|
||||
MAINTAINER datarhei <info@datarhei.org>
|
||||
|
||||
ENV NODE_VERSION 4.2.6
|
||||
ENV NPM_VERSION 2.14.12
|
||||
ENV NODE_VERSION 5.7.1
|
||||
ENV NPM_VERSION 3.6.0
|
||||
|
||||
ENV FFMPEG_VERSION 2.8.5
|
||||
ENV FFMPEG_VERSION 2.8.6
|
||||
ENV YASM_VERSION 1.3.0
|
||||
ENV LAME_VERSION 3_99_5
|
||||
ENV NGINX_VERSION 1.9.9
|
||||
ENV NGINX_RTMP_VERSION 1.1.7
|
||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
||||
|
||||
ENV SRC /usr/local
|
||||
ENV LD_LIBRARY_PATH ${SRC}/lib
|
||||
ENV PKG_CONFIG_PATH ${SRC}/lib/pkgconfig
|
||||
ENV SRC "/usr/local"
|
||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
||||
|
||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake perl build-essential libpcre3-dev"
|
||||
ENV 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 curl git libpcre3 tar ${BUILDDEPS}
|
||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
||||
|
||||
# node
|
||||
RUN DIR=$(mktemp -d) && cd ${DIR} && \
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
set -x && \
|
||||
curl -LOks https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-armv6l.tar.gz && \
|
||||
tar -xzf "node-v$NODE_VERSION-linux-armv6l.tar.gz" -C /usr/local --strip-components=1 && \
|
||||
npm install -g npm@"$NPM_VERSION" --unsafe-perm && \
|
||||
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" \
|
||||
-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}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# yasm
|
||||
RUN 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} && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# x264
|
||||
RUN DIR=$(mktemp -d) && cd ${DIR} && \
|
||||
git clone --depth 1 git://git.videolan.org/x264 && \
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||
cd x264 && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" --enable-static && \
|
||||
make && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-static \
|
||||
--disable-cli && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# libmp3lame
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --bindir="${SRC}/bin" --disable-shared --enable-nasm && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-nasm \
|
||||
--disable-shared && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean&& \
|
||||
rm -rf ${DIR}
|
||||
make distclean && \
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# ffmpeg
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --extra-cflags="-I${SRC}/include" --extra-ldflags="-L${SRC}/lib" --bindir="${SRC}/bin" \
|
||||
--extra-libs=-ldl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-gpl \
|
||||
--enable-postproc --enable-nonfree --enable-avresample --disable-debug --enable-small --enable-openssl \
|
||||
--disable-doc --disable-ffserver && \
|
||||
make && \
|
||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
curl -Lks "https://github.com/FFmpeg/FFmpeg/commit/1c7e2cf9d33968375ee4025d2279c937e147dae2.patch" | patch -p1 && \
|
||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||
./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}
|
||||
RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/libc.conf
|
||||
cp qt-faststart "${SRC}/bin" && \
|
||||
rm -rf "${DIR}"
|
||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
||||
RUN ffmpeg -buildconf
|
||||
|
||||
# nginx-rtmp
|
||||
RUN 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/arut/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 && \
|
||||
RUN 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}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||
rm -rf /tmp/*
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --force-yes git
|
||||
|
||||
COPY . /restreamer
|
||||
WORKDIR /restreamer
|
||||
|
||||
RUN npm install -g bower grunt-bower grunt-cli public-ip && \
|
||||
RUN npm install -g bower grunt grunt-cli nodemon public-ip && \
|
||||
npm install && \
|
||||
grunt build && \
|
||||
npm prune --production
|
||||
npm prune --production && \
|
||||
npm cache clean && \
|
||||
bower cache clean --allow-root
|
||||
|
||||
ENV RESTREAMER_USERNAME admin
|
||||
ENV RESTREAMER_PASSWORD datarhei
|
||||
ENV RS_USERNAME admin
|
||||
ENV RS_PASSWORD datarhei
|
||||
|
||||
EXPOSE 8080
|
||||
VOLUME ["/restreamer/db"]
|
||||
|
||||
@@ -1,114 +1,145 @@
|
||||
FROM armbuild/debian:jessie
|
||||
FROM resin/rpi-raspbian:jessie
|
||||
|
||||
MAINTAINER datarhei <info@datarhei.org>
|
||||
|
||||
ENV NODE_VERSION 4.2.6
|
||||
ENV NPM_VERSION 2.14.12
|
||||
ENV NODE_VERSION 5.7.1
|
||||
ENV NPM_VERSION 3.6.0
|
||||
|
||||
ENV FFMPEG_VERSION 2.8.5
|
||||
ENV FFMPEG_VERSION 2.8.6
|
||||
ENV YASM_VERSION 1.3.0
|
||||
ENV LAME_VERSION 3_99_5
|
||||
ENV NGINX_VERSION 1.9.9
|
||||
ENV NGINX_RTMP_VERSION 1.1.7
|
||||
ENV NGINX_RTMP_VERSION 1.1.7.10
|
||||
|
||||
ENV SRC /usr/local
|
||||
ENV LD_LIBRARY_PATH ${SRC}/lib
|
||||
ENV PKG_CONFIG_PATH ${SRC}/lib/pkgconfig
|
||||
ENV SRC "/usr/local"
|
||||
ENV LD_LIBRARY_PATH "${SRC}/lib"
|
||||
ENV PKG_CONFIG_PATH "${SRC}/lib/pkgconfig"
|
||||
|
||||
ENV BUILDDEPS "autoconf automake gcc g++ libtool make nasm zlib1g-dev libssl-dev xz-utils cmake perl build-essential libpcre3-dev"
|
||||
ENV 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 curl git libpcre3 tar ${BUILDDEPS}
|
||||
apt-get install -y --force-yes curl git libpcre3 tar perl ca-certificates ${BUILDDEPS}
|
||||
|
||||
# node
|
||||
RUN DIR=$(mktemp -d) && cd ${DIR} && \
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
set -x && \
|
||||
curl -LOks https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-armv7l.tar.gz && \
|
||||
tar -xzf "node-v$NODE_VERSION-linux-armv7l.tar.gz" -C /usr/local --strip-components=1 && \
|
||||
npm install -g npm@"$NPM_VERSION" --unsafe-perm && \
|
||||
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" \
|
||||
-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}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# yasm
|
||||
RUN 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} && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# x264
|
||||
RUN DIR=$(mktemp -d) && cd ${DIR} && \
|
||||
git clone --depth 1 git://git.videolan.org/x264 && \
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
git clone --depth 1 "git://git.videolan.org/x264" && \
|
||||
cd x264 && \
|
||||
./configure --prefix="$SRC" --bindir="${SRC}/bin" --enable-static && \
|
||||
make && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-static \
|
||||
--disable-cli && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean && \
|
||||
rm -rf ${DIR}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# libmp3lame
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --bindir="${SRC}/bin" --disable-shared --enable-nasm && \
|
||||
make && \
|
||||
RUN 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}" && \
|
||||
./configure \
|
||||
--prefix="${SRC}" \
|
||||
--bindir="${SRC}/bin" \
|
||||
--enable-nasm \
|
||||
--disable-shared && \
|
||||
make -j"$(nproc)" && \
|
||||
make install && \
|
||||
make distclean&& \
|
||||
rm -rf ${DIR}
|
||||
make distclean && \
|
||||
rm -rf "${DIR}"
|
||||
|
||||
# ffmpeg
|
||||
RUN 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} && \
|
||||
./configure --prefix="${SRC}" --extra-cflags="-I${SRC}/include" --extra-ldflags="-L${SRC}/lib" --bindir="${SRC}/bin" \
|
||||
--extra-libs=-ldl --enable-version3 --enable-libmp3lame --enable-libx264 --enable-gpl \
|
||||
--enable-postproc --enable-nonfree --enable-avresample --disable-debug --enable-small --enable-openssl \
|
||||
--disable-doc --disable-ffserver && \
|
||||
make && \
|
||||
# patch: andrew-shulgin Ignore invalid sprop-parameter-sets missing PPS
|
||||
RUN DIR="$(mktemp -d)" && cd "${DIR}" && \
|
||||
curl -LOks "https://ffmpeg.org/releases/ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
tar xzvf "ffmpeg-${FFMPEG_VERSION}.tar.gz" && \
|
||||
curl -Lks "https://github.com/FFmpeg/FFmpeg/commit/1c7e2cf9d33968375ee4025d2279c937e147dae2.patch" | patch -p1 && \
|
||||
cd "ffmpeg-${FFMPEG_VERSION}" && \
|
||||
./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}
|
||||
RUN echo "/usr/local/lib" > /etc/ld.so.conf.d/libc.conf
|
||||
cp qt-faststart "${SRC}/bin" && \
|
||||
rm -rf "${DIR}"
|
||||
RUN echo "${SRC}/lib" > "/etc/ld.so.conf.d/libc.conf"
|
||||
RUN ffmpeg -buildconf
|
||||
|
||||
# nginx-rtmp
|
||||
RUN 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/arut/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 && \
|
||||
RUN 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}
|
||||
rm -rf "${DIR}"
|
||||
|
||||
RUN apt-get purge -y --auto-remove ${BUILDDEPS} && \
|
||||
apt-get install -y git && \
|
||||
rm -rf /tmp/*
|
||||
|
||||
COPY . /restreamer
|
||||
WORKDIR /restreamer
|
||||
|
||||
RUN npm install -g bower grunt-bower grunt-cli public-ip && \
|
||||
RUN npm install -g bower grunt grunt-cli nodemon public-ip && \
|
||||
npm install && \
|
||||
grunt build && \
|
||||
npm prune --production
|
||||
npm prune --production && \
|
||||
npm cache clean && \
|
||||
bower cache clean --allow-root
|
||||
|
||||
ENV RESTREAMER_USERNAME admin
|
||||
ENV RESTREAMER_PASSWORD datarhei
|
||||
ENV RS_USERNAME admin
|
||||
ENV RS_PASSWORD datarhei
|
||||
|
||||
EXPOSE 8080
|
||||
VOLUME ["/restreamer/db"]
|
||||
|
||||
22
README.md
22
README.md
@@ -1,30 +1,46 @@
|
||||
#Restreamer
|
||||
|
||||
Datarhei/Restreamer offers smart free video streaming in real time. Stream H.264 video of IP cameras live to your website. Upload your live video on [YouTube-Live](https://www.youtube.com/), [Ustream](http://www.ustream.tv/), [Twitch](http://www.twitch.tv/), [Livestream.com](http://livestream.com/) or any other streaming solutions e.g. [Wowza-Streaming-Engine](https://www.wowza.com/). Our [Docker-Image](https://hub.docker.com/search/?q=restreamer&page=1&isAutomated=0&isOfficial=0&starCount=0&pullCount=0) is easy to install and runs on Linux, MacOS and Windows. Datarhei/Restreamer can be perfectly combined with single-board computers like [Raspberry Pi](https://www.raspberrypi.org/) and [Odroid](http://www.hardkernel.com/main/main.php). It is free (licensed under Apache 2.0) and you can use it for any purpose, private or commercial.
|
||||
|
||||
##Features
|
||||
|
||||
- User-Interface including login-security
|
||||
- JSON / HTTP-API
|
||||
- <a target= "_blank" href="http://ffmpeg.org/">FFmpeg</a> streaming/encoding the video/camera-stream, creating snapshots or pushing to a external streaming-endpoint
|
||||
- <a target= "_blank" href="http://nginx.org/">NGINX</a> incl. <a target= "_blank" href="https://github.com/arut/nginx-rtmp-module">RTMP-Module</a> as streaming-backend and hls server
|
||||
- <a target= "_blank" href="http://nginx.org/">NGINX</a> incl. <a target= "_blank" href="https://github.com/sergey-dryabzhinsky/nginx-rtmp-module">RTMP-Module</a> as streaming-backend and hls server
|
||||
- <a target= "_blank" href="https://github.com/clappr/clappr">Clappr-Player</a> to embed your stream on your website
|
||||
- <a target= "_blank" href="https://www.docker.com/">Docker</a> and <a target= "_blank" href="https://kitematic.com/">Kitematic (Docker-Toolbox)</a> optimizations and very easy installation
|
||||
|
||||
##Roadmap
|
||||
- RC6 (tba)
|
||||
## Upcomming releases
|
||||
|
||||
- RC7 (tba)
|
||||
|
||||
## Roadmap
|
||||
|
||||
- optimizing FFmpeg handling
|
||||
- backend refactoring
|
||||
- full REST API
|
||||
- security improvements
|
||||
|
||||
##Documentation
|
||||
|
||||
Documentation is available on [Datarhei/Restreamer GitHub pages](https://datarhei.github.io/restreamer/).
|
||||
We give you a lot of of informations from setting up a camera, embedding your player upon your website and streaming to services like e.g. YouTube-Live, Ustream and Livestream.com and many more things.
|
||||
|
||||
More additional informations about streaming, cameras and so on you can find in our [Wiki](https://datarhei.github.com/restreamer/wiki).
|
||||
|
||||
##Help / Bugs
|
||||
|
||||
If you have problems or found a bug feel free to create a new issue upon the <a target= "_blank" href="https://github.com/datarhei/restreamer/issues">Github issue management</a>.
|
||||
|
||||
Want to talk to us? Write an email to <a href="mailto:open@datarhei.org?subject=Datarhei/Restreamer">open@datarhei.org</a>, go to [Support](../support.html) or choose a nickname speak to us in IRC: <a href="irc://irc.freenode.net#piwik">irc.freenode.net/#datarhei</a> (<a target= "_blank" href="https://webchat.freenode.net/?channels=datarhei">webchat</a>). You could ask a question in our (<a target= "_blank" href="https://groups.google.com/forum/#!forum/datarhei">Forum</a>) on Google Groups, too.
|
||||
|
||||
##Authors
|
||||
|
||||
The Datarhei/Restreamer was created by [Julius Eitzen](https://github.com/jeitzen), [Sven Erbeck](https://github.com/svenerbeck), [Christoph Johannsdotter](https://github.com/christophjohannsdotter) and [Jan Stabenow](https://github.com/jstabenow).
|
||||
|
||||
Special thanks for supporting this project continuously to [Andrew Shulgin](https://github.com/andrew-shulgin).
|
||||
|
||||
##Copyright
|
||||
|
||||
Code released under the [Apache license](LICENSE). Images are copyrighted by datarhei.org
|
||||
|
||||
12
bower.json
12
bower.json
@@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "Restreamer",
|
||||
"version": "0.1.0-RC5",
|
||||
"version": "0.1.0-RC6",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bootstrap": "3.3.6",
|
||||
"jquery": "2.2.0",
|
||||
"jquery": "2.2.1",
|
||||
"html5shiv": "3.7.3",
|
||||
"respond": "1.4.2",
|
||||
"angular-bootstrap": "~0.14.3",
|
||||
"angular-animate": "1.4.9",
|
||||
"ui-router": "~0.2.15",
|
||||
"angular-translate": "~2.9.0",
|
||||
"angular-translate-loader-static-files": "~2.9.0"
|
||||
"angular-animate": "1.5.0",
|
||||
"ui-router": "0.2.18",
|
||||
"angular-translate": "2.9.2",
|
||||
"angular-translate-loader-static-files": "2.9.2"
|
||||
},
|
||||
"resolutions": {
|
||||
"angular": "1.4.8",
|
||||
|
||||
@@ -21,6 +21,16 @@
|
||||
"optionalOutputAddress"
|
||||
]
|
||||
},
|
||||
"options": {
|
||||
"id": "http://jsonschema.net/options",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rtspTcp": {
|
||||
"id": "http://jsonschema.net/options/rtspTcp",
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"states": {
|
||||
"id": "http://jsonschema.net/states",
|
||||
"type": "object",
|
||||
@@ -64,6 +74,7 @@
|
||||
},
|
||||
"required": [
|
||||
"addresses",
|
||||
"options",
|
||||
"states",
|
||||
"userActions"
|
||||
]
|
||||
|
||||
@@ -7,23 +7,21 @@
|
||||
},
|
||||
"ffmpeg": {
|
||||
"options": {
|
||||
"native_h264":[
|
||||
"-c copy",
|
||||
"native_h264": [
|
||||
"-codec copy",
|
||||
"-map_metadata -1",
|
||||
"-metadata application=datarhei/Restreamer",
|
||||
"-metadata server=NGINX-RTMP",
|
||||
"-f flv"
|
||||
],
|
||||
"native_h264_soundless_aac":[
|
||||
"-ar 44100",
|
||||
"-ac 2",
|
||||
"-acodec pcm_s16le",
|
||||
"-f s16le",
|
||||
"-ac 2",
|
||||
"-i /dev/zero",
|
||||
"-c:v copy",
|
||||
"native_h264_soundless_aac": [
|
||||
"-f lavfi",
|
||||
"-i aevalsrc=0",
|
||||
"-vcodec copy",
|
||||
"-acodec aac",
|
||||
"-ab 128k",
|
||||
"-map 0:0",
|
||||
"-map 1:0",
|
||||
"-shortest",
|
||||
"-map_metadata -1",
|
||||
"-metadata application=datarhei/Restreamer",
|
||||
"-metadata server=NGINX-RTMP",
|
||||
@@ -43,5 +41,71 @@
|
||||
"rtmp_port": "1935",
|
||||
"rtmp_hls_path": "/hls/"
|
||||
}
|
||||
},
|
||||
"envVars": [
|
||||
{
|
||||
"name": "RS_NODE_PORT",
|
||||
"alias": "NODEJS_PORT",
|
||||
"type": "int",
|
||||
"defaultValue": "3000",
|
||||
"required": false,
|
||||
"description": "Webserver port of application"
|
||||
},
|
||||
{
|
||||
"name": "RS_NODE_ENV",
|
||||
"alias": "NODE_ENV",
|
||||
"type": "string",
|
||||
"defaultValue": "prod",
|
||||
"required": false,
|
||||
"description": "Nodejs Environment"
|
||||
},
|
||||
{
|
||||
"name": "RS_LOGGER_LEVEL",
|
||||
"alias": "LOGGER_LEVEL",
|
||||
"type": "int",
|
||||
"defaultValue": "3",
|
||||
"required": true,
|
||||
"description": "Logger level to defined, what should be logged"
|
||||
},
|
||||
{
|
||||
"name": "RS_TIMEZONE",
|
||||
"alias": "TIMEZONE",
|
||||
"type": "string",
|
||||
"defaultValue": "Europe/Berlin",
|
||||
"required": true,
|
||||
"description": "Set the timezone"
|
||||
},
|
||||
{
|
||||
"name": "RS_SNAPSHOT_REFRESH_INTERVAL",
|
||||
"alias": "SNAPSHOT_REFRESH_INTERVAL",
|
||||
"type": "string",
|
||||
"defaultValue": "1m",
|
||||
"required": false,
|
||||
"description": "Interval to create a new Snapshot (in minutes)"
|
||||
},
|
||||
{
|
||||
"name": "RS_CREATE_HEAPDUMPS",
|
||||
"alias": "CREATE_HEAPDUMPS",
|
||||
"type": "bool",
|
||||
"defaultValue": "false",
|
||||
"required": false,
|
||||
"description": "Create Heapdumps of application"
|
||||
},
|
||||
{
|
||||
"name": "RS_USERNAME",
|
||||
"alias": "RESTREAMER_USERNAME",
|
||||
"type": "string",
|
||||
"defaultValue": "admin",
|
||||
"required": false,
|
||||
"description": "Backend user name"
|
||||
},
|
||||
{
|
||||
"name": "RS_PASSWORD",
|
||||
"alias": "RESTREAMER_PASSWORD",
|
||||
"type": "string",
|
||||
"defaultValue": "datarhei",
|
||||
"required": false,
|
||||
"description": "Backend login password"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -10,18 +10,14 @@ rtmp {
|
||||
application live {
|
||||
live on;
|
||||
meta copy;
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
}
|
||||
application hls {
|
||||
live on;
|
||||
hls on;
|
||||
hls_type live;
|
||||
hls_playlist_length 60s;
|
||||
hls_fragment 2s;
|
||||
hls_path /tmp/hls;
|
||||
meta copy;
|
||||
allow publish 127.0.0.1;
|
||||
deny publish all;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,6 +26,12 @@ http {
|
||||
tcp_nopush on;
|
||||
server {
|
||||
listen 8080;
|
||||
location ~ ^/(libs|locales|images|dist|help|css|scripts|player.html|crossdomain.xml) {
|
||||
root /restreamer/src/webserver/public;
|
||||
allow all;
|
||||
include /usr/local/nginx/conf/mime.types;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
@@ -37,20 +39,6 @@ http {
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
}
|
||||
location /player.html {
|
||||
root /restreamer/bin/webserver/public;
|
||||
allow all;
|
||||
}
|
||||
location /libs {
|
||||
root /restreamer/bin/webserver/public;
|
||||
allow all;
|
||||
include /usr/local/nginx/conf/mime.types;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
location /images {
|
||||
root /restreamer/bin/webserver/public;
|
||||
allow all;
|
||||
}
|
||||
location /hls {
|
||||
types {
|
||||
application/vnd.apple.mpegurl m3u8;
|
||||
|
||||
120
gruntfile.js
120
gruntfile.js
@@ -1,44 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function(grunt) {
|
||||
// files for this project
|
||||
var files = {
|
||||
compiledFrontendJS: ['bin/webserver/public/scripts/**/*.js', 'bin/executors/**/gui/js/*.js'],
|
||||
es6Src: ['**/*.js'],
|
||||
stylesheets: ['static/webserver/public/css/*.css']
|
||||
};
|
||||
// path to store the transpiled es6 files
|
||||
const transpiledPath = "src/webserver/transpiled/";
|
||||
|
||||
const files = {
|
||||
//workaround to keep correct order
|
||||
transpiledFrontendJs: [
|
||||
`${transpiledPath}/webserver/public/scripts/App.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/App.Config.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/Main/MainModule.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Main/MainController.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/Login/LoginModule.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Login/LoginController.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/Header/HeaderModule.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Header/HeaderController.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Header/HeaderDirective.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/Footer/FooterModule.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Footer/FooterController.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Footer/FooterDirective.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/StreamingInterface/StreamingInterfaceModule.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/StreamingInterface/StreamingStatusController.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/StreamingInterface/StreamingStatusDirective.js`,
|
||||
|
||||
`${transpiledPath}/webserver/public/scripts/Shared/LoggerService.js`,
|
||||
`${transpiledPath}/webserver/public/scripts/Shared/WebsocketsService.js`
|
||||
],
|
||||
es6Src: [
|
||||
'webserver/public/scripts/**/*.js'
|
||||
],
|
||||
stylesheets: ['src/webserver/public/css/*.css']
|
||||
};
|
||||
|
||||
module.exports = function (grunt) {
|
||||
|
||||
// Project Configuration
|
||||
grunt.initConfig({
|
||||
|
||||
/*
|
||||
Watcher config with livereload
|
||||
*/
|
||||
watch: {
|
||||
scripts: {
|
||||
files: ['src/**/*.js'],
|
||||
tasks: ['compile-code'],
|
||||
options: {
|
||||
interrupt: true,
|
||||
livereload: {
|
||||
host: 'localhost',
|
||||
port: 5000
|
||||
}
|
||||
}
|
||||
},
|
||||
statics: {
|
||||
files: ['static/**/*.html'],
|
||||
tasks: ['copy-statics'],
|
||||
options: {
|
||||
interrupt: true,
|
||||
livereload: {
|
||||
host: 'localhost',
|
||||
port: 5000
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
Config for shell commands
|
||||
*/
|
||||
@@ -46,14 +48,11 @@ module.exports = function(grunt) {
|
||||
start: {
|
||||
command: 'npm start'
|
||||
},
|
||||
removeOldBinFolder: {
|
||||
command: 'rm -Rf bin/'
|
||||
removeTempTranspilingFolder: {
|
||||
command: `rm -Rf ${transpiledPath}`
|
||||
},
|
||||
createBinFolder: {
|
||||
command: 'mkdir bin/'
|
||||
},
|
||||
copyStatics: {
|
||||
command: 'cp -R static/* bin'
|
||||
createTempTranspilingFolder: {
|
||||
command: `mkdir ${transpiledPath}`
|
||||
},
|
||||
bower: {
|
||||
command: 'bower install --allow-root'
|
||||
@@ -63,7 +62,7 @@ module.exports = function(grunt) {
|
||||
},
|
||||
//temp workaround - https://github.com/clappr/clappr/issues/709
|
||||
clappr: {
|
||||
command: 'curl -LOks https://github.com/clappr/clappr/archive/master.tar.gz && tar xzvf master.tar.gz && rm master.tar.gz && mv clappr-master bin/webserver/public/libs/clappr'
|
||||
command: 'rm -rf src/webserver/public/libs/clappr && git clone --depth 1 git://github.com/clappr/clappr src/webserver/public/libs/clappr'
|
||||
}
|
||||
},
|
||||
|
||||
@@ -80,8 +79,8 @@ module.exports = function(grunt) {
|
||||
{
|
||||
expand: true,
|
||||
cwd: 'src/',
|
||||
src:'<%= es6Src %>',
|
||||
dest: 'bin/'
|
||||
src: '<%= es6Src %>',
|
||||
dest: transpiledPath
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -105,7 +104,7 @@ module.exports = function(grunt) {
|
||||
csslintrc: '.csslintrc'
|
||||
},
|
||||
all: {
|
||||
src: ['static/webserver/public/css/*.css']
|
||||
src: ['src/webserver/public/css/*.css']
|
||||
}
|
||||
},
|
||||
|
||||
@@ -118,7 +117,7 @@ module.exports = function(grunt) {
|
||||
mangle: true
|
||||
},
|
||||
files: {
|
||||
'bin/webserver/public/dist/application.min.js': 'bin/webserver/public/dist/application.js'
|
||||
'src/webserver/public/dist/application.min.js': 'src/webserver/public/dist/application.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -129,7 +128,7 @@ module.exports = function(grunt) {
|
||||
cssmin: {
|
||||
combine: {
|
||||
files: {
|
||||
'bin/webserver/public/css/restreamer.min.css': '<%= stylesheets %>'
|
||||
'src/webserver/public/css/restreamer.min.css': '<%= stylesheets %>'
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -140,7 +139,7 @@ module.exports = function(grunt) {
|
||||
ngAnnotate: {
|
||||
production: {
|
||||
files: {
|
||||
'bin/webserver/public/dist/application.js': '<%= compiledFrontendJS %>'
|
||||
'src/webserver/public/dist/application.js': '<%= transpiledFrontendJs %>'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,9 +149,9 @@ module.exports = function(grunt) {
|
||||
Load NPM tasks
|
||||
*/
|
||||
require('load-grunt-tasks')(grunt);
|
||||
grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function() {
|
||||
grunt.task.registerTask('loadConfig', 'Task that loads the config into a grunt option.', function () {
|
||||
grunt.config.set('es6Src', files.es6Src);
|
||||
grunt.config.set('compiledFrontendJS', files.compiledFrontendJS);
|
||||
grunt.config.set('transpiledFrontendJs', files.transpiledFrontendJs);
|
||||
grunt.config.set('stylesheets', files.stylesheets);
|
||||
});
|
||||
grunt.loadNpmTasks('grunt-shell');
|
||||
@@ -163,8 +162,8 @@ module.exports = function(grunt) {
|
||||
*/
|
||||
// lint
|
||||
grunt.registerTask('lint', ['csslint', 'shell:eslint']);
|
||||
// clear old bin folder and create new one
|
||||
grunt.registerTask('clearOldBuild', ['shell:removeOldBinFolder', 'shell:createBinFolder']);
|
||||
// clear old transpile folder and create new one
|
||||
grunt.registerTask('clearOldBuild', ['shell:removeTempTranspilingFolder', 'shell:createTempTranspilingFolder']);
|
||||
// install frontendlibraries (atm through bower)
|
||||
grunt.registerTask('installFrontendLibraries', ['shell:bower', 'shell:clappr']);
|
||||
// minify the frontend files
|
||||
@@ -173,17 +172,16 @@ module.exports = function(grunt) {
|
||||
/*
|
||||
Build Tasks
|
||||
*/
|
||||
grunt.registerTask('build', ['loadConfig','clearOldBuild', 'shell:copyStatics', 'babel', 'minifyFrontendFiles', 'installFrontendLibraries']);
|
||||
grunt.registerTask('compile-code', ['loadConfig','babel', 'shell:copyStatics', 'minifyFrontendFiles']);
|
||||
grunt.registerTask('copy-statics', ['loadConfig', 'shell:copyStatics']);
|
||||
grunt.registerTask('build', ['loadConfig', 'clearOldBuild', 'babel', 'minifyFrontendFiles', 'installFrontendLibraries', 'shell:removeTempTranspilingFolder']);
|
||||
|
||||
/*
|
||||
Just Compile
|
||||
*/
|
||||
grunt.registerTask('compile', ['loadConfig', 'clearOldBuild', 'babel', 'minifyFrontendFiles']);
|
||||
|
||||
/*
|
||||
Run Tasks
|
||||
*/
|
||||
// run current build in /bin
|
||||
grunt.registerTask('run', ['shell:start']);
|
||||
// rebuild and run
|
||||
grunt.registerTask('run-clean', ['build', 'run']);
|
||||
// update code and run
|
||||
grunt.registerTask('run-update-code', ['loadConfig','babel','shell:copyStatics', 'minifyFrontendFiles', 'run'])
|
||||
|
||||
};
|
||||
|
||||
33
package.json
33
package.json
@@ -1,23 +1,26 @@
|
||||
{
|
||||
"name": "Restreamer",
|
||||
"version": "0.1.0-RC5",
|
||||
"version": "0.1.0-RC6",
|
||||
"description": "Allows you to do h.264 real-time video streaming on your website without a streaming provider",
|
||||
"author": "datarhei.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/datarhei/restreamer.git"
|
||||
},
|
||||
"license": "Apache-2.0",
|
||||
"scripts": {
|
||||
"start": "node ./bin/start"
|
||||
"start": "node ./src/start"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "1.14.2",
|
||||
"compression": "~1.6.0",
|
||||
"connect-flash": "^0.1.1",
|
||||
"body-parser": "1.15.0",
|
||||
"compression": "~1.6.1",
|
||||
"cookie-parser": "1.4.1",
|
||||
"express": "4.13.4",
|
||||
"express-session": "^1.12.1",
|
||||
"express-session": "^1.13.0",
|
||||
"fluent-ffmpeg": "git://github.com/datarhei/node-fluent-ffmpeg",
|
||||
"jsonschema": "^1.0.2",
|
||||
"jsonschema": "^1.1.0",
|
||||
"moment-timezone": "^0.5.0",
|
||||
"node-json-db": "^0.5.1",
|
||||
"node-json-db": "git://github.com/andrew-shulgin/node-json-db",
|
||||
"passport": "^0.3.2",
|
||||
"passport-local": "^1.0.0",
|
||||
"ps-find": "^1.1.0",
|
||||
@@ -25,16 +28,16 @@
|
||||
"socket.io": "1.4.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-preset-es2015": "^6.1.2",
|
||||
"eslint": "^2.0.0-beta.3",
|
||||
"grunt": "^0.4.5",
|
||||
"eslint": "2.3.0",
|
||||
"babel-preset-es2015": "^6.5.0",
|
||||
"grunt": "0.4.5",
|
||||
"grunt-babel": "^6.0.0",
|
||||
"grunt-contrib-csslint": "^0.5.0",
|
||||
"grunt-contrib-cssmin": "~0.14.0",
|
||||
"grunt-contrib-uglify": "~0.11.0",
|
||||
"grunt-contrib-csslint": "^1.0.0",
|
||||
"grunt-contrib-cssmin": "~1.0.0",
|
||||
"grunt-contrib-uglify": "~1.0.0",
|
||||
"grunt-contrib-watch": "^0.6.1",
|
||||
"grunt-ng-annotate": "~1.0.1",
|
||||
"grunt-shell": "^1.1.2",
|
||||
"grunt-shell": "1.2.1",
|
||||
"load-grunt-tasks": "~3.4.0"
|
||||
}
|
||||
}
|
||||
|
||||
4
run.sh
4
run.sh
@@ -15,7 +15,7 @@ then
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
/opt/vc/bin/raspivid -t 0 -w 1280 -h 720 -fps 25 -b 500000 -o - | ffmpeg -i - -c copy -f flv rtmp://127.0.0.1:1935/live/raspicam.stream
|
||||
/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
|
||||
elif [ "${MODE}" == "USBCAM" ];
|
||||
then
|
||||
apt-get update && apt-get install -y v4l-utils libv4l-0
|
||||
@@ -30,7 +30,7 @@ then
|
||||
sleep 5
|
||||
fi
|
||||
done
|
||||
ffmpeg -f v4l2 -r 25 -s 1280x720 -i /dev/video0 -f flv rtmp://127.0.0.1:1935/live/usb.stream
|
||||
ffmpeg -f v4l2 -r 25 -s 1280x720 -i /dev/video0 -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/usb.stream > /dev/null 2>&1
|
||||
else
|
||||
npm start
|
||||
fi
|
||||
|
||||
@@ -4,24 +4,51 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
const logger = require('./Logger')('EnvVar');
|
||||
|
||||
/**
|
||||
* Class for environment variables with default values
|
||||
*/
|
||||
class EnvVar {
|
||||
static init (config) {
|
||||
var killProcess = false;
|
||||
|
||||
/**
|
||||
* constructs an envvar
|
||||
* @param {string} name
|
||||
* @param {string} required
|
||||
* @param {string} defaultValue
|
||||
* @param {string} description
|
||||
*/
|
||||
constructor (name, required, defaultValue, description) {
|
||||
this.name = name;
|
||||
this.required = required;
|
||||
this.defaultValue = defaultValue;
|
||||
this.description = description;
|
||||
for (let envVar of config.envVars) {
|
||||
if (typeof process.env[envVar.alias] !== 'undefined') {
|
||||
process.env[envVar.name] = process.env[envVar.alias];
|
||||
delete process.env[envVar.alias];
|
||||
}
|
||||
|
||||
if (typeof process.env[envVar.name] !== 'undefined') {
|
||||
logger.info(`ENV "${envVar.name} = ${process.env[envVar.name]}"`, envVar.description);
|
||||
} else if (envVar.required === true) {
|
||||
logger.error(`No value set for env "${envVar.name}", but it is required`);
|
||||
killProcess = true;
|
||||
} else {
|
||||
process.env[envVar.name] = envVar.defaultValue;
|
||||
logger.info(`ENV "${envVar.name} = ${process.env[envVar.name]}", set to default value`, envVar.description);
|
||||
}
|
||||
|
||||
if (typeof process.env[envVar.name] !== 'undefined') {
|
||||
switch (envVar.type) {
|
||||
case 'int':
|
||||
process.env[envVar.name] = parseInt(process.env[envVar.name], 10);
|
||||
break;
|
||||
case 'bool':
|
||||
process.env[envVar.name] = process.env[envVar.name] === 'true';
|
||||
break;
|
||||
default: // keep strings
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (killProcess === true) {
|
||||
setTimeout(()=> {
|
||||
process.exit();
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,18 +4,19 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const moment = require('moment-timezone');
|
||||
const LEVEL_MUTE = 0;
|
||||
const LEVEL_ERROR = 1;
|
||||
const LEVEL_WARN = 2;
|
||||
const LEVEL_INFO = 3;
|
||||
const LEVEL_DEBUG = 4;
|
||||
|
||||
// set default timezone to use the timezone before the default values are
|
||||
process.env.TIMEZONE = process.env.TIMEZONE ? process.env.TIMEZONE : 'Europe/Berlin';
|
||||
if (typeof process.env.LOGGER_LEVEL === 'undefined') {
|
||||
process.env.LOGGER_LEVEL = '3';
|
||||
}
|
||||
// @todo: it is really ugly and wrong to log with hardcoded timezone before environment is read
|
||||
process.env.RS_TIMEZONE = process.env.RS_TIMEZONE || 'Europe/Berlin';
|
||||
process.env.RS_LOGGER_LEVEL = process.env.RS_LOGGER_LEVEL || 3;
|
||||
|
||||
/**
|
||||
* Class for logger
|
||||
@@ -27,7 +28,7 @@ class Logger {
|
||||
* @returns {boolean}
|
||||
*/
|
||||
static isMuted () {
|
||||
return !!process.env.LOGGER_MUTED;
|
||||
return process.env.RS_LOGGER_LEVEL === LEVEL_MUTE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,7 @@ class Logger {
|
||||
* @param {string} context context of the log message (classname.methodname)
|
||||
*/
|
||||
constructor (context) {
|
||||
process.env.RS_LOGGER_LEVEL = process.env.RS_LOGGER_LEVEL || LEVEL_INFO;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@@ -45,76 +47,100 @@ class Logger {
|
||||
* @param {string} type
|
||||
*/
|
||||
stdout (message, context, type) {
|
||||
var time = moment().tz(process.env.RS_TIMEZONE).format('DD-MM-YYYY HH:mm:ss.SSS');
|
||||
var loggerContext = `${String(context)}`;
|
||||
|
||||
if (Logger.isMuted()) {
|
||||
return;
|
||||
}
|
||||
if (context === false) {
|
||||
context = '';
|
||||
if (context) {
|
||||
process.stdout.write(`[${time}] [${type}] ${message} [${loggerContext}]\n`);
|
||||
} else {
|
||||
context = '(' + context + ')';
|
||||
process.stdout.write(`[${time}] [${type}] ${message}\n`);
|
||||
}
|
||||
var str = '[' + (moment().tz(process.env.TIMEZONE).format('DD-MM-YYYY HH:mm:ss.SSS')) + '] [' + type + '] ' + message + ' ' + context;
|
||||
|
||||
console.log(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* print an info message if LOG_LEVEL >= LEVEL_INFO
|
||||
* @param {string} message
|
||||
* @param {string} context
|
||||
* @param {string=} context
|
||||
* @param {boolean=} alertGui
|
||||
*/
|
||||
info (message, context, alertGui) {
|
||||
var loggerContext = context;
|
||||
var loggerAlertGui = alertGui;
|
||||
|
||||
if (typeof context === 'undefined') {
|
||||
context = this.context;
|
||||
loggerContext = this.context;
|
||||
}
|
||||
|
||||
if (typeof alertGui === 'undefined') {
|
||||
alertGui = false;
|
||||
loggerAlertGui = false;
|
||||
}
|
||||
if (process.env.LOGGER_LEVEL >= LEVEL_INFO) {
|
||||
return this.stdout(message, context, 'INFO');
|
||||
|
||||
if (process.env.RS_LOGGER_LEVEL >= LEVEL_INFO) {
|
||||
return this.stdout(message, loggerContext, 'INFO');
|
||||
}
|
||||
if (alertGui) {
|
||||
// todo: if alertGui is activated on frontend and websocketcontroller, insert emit here
|
||||
|
||||
// todo: if alertGui is activated on frontend and websockets controller, insert emit here
|
||||
if (loggerAlertGui) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* print a warning message if LOG_LEVEL >= LEVEL_WARN
|
||||
* @param {string} message
|
||||
* @param {string} context
|
||||
* @param {string=} context
|
||||
* @param {boolean=} alertGui
|
||||
*/
|
||||
warn (message, context, alertGui) {
|
||||
var loggerContext = context;
|
||||
var loggerAlertGui = alertGui;
|
||||
|
||||
if (typeof context === 'undefined') {
|
||||
context = this.context;
|
||||
loggerContext = this.context;
|
||||
}
|
||||
|
||||
if (typeof alertGui === 'undefined') {
|
||||
alertGui = false;
|
||||
loggerAlertGui = false;
|
||||
}
|
||||
if (process.env.LOGGER_LEVEL >= LEVEL_WARN) {
|
||||
return this.stdout(message, context, 'WARN');
|
||||
|
||||
if (process.env.RS_LOGGER_LEVEL >= LEVEL_WARN) {
|
||||
return this.stdout(message, loggerContext, 'WARN');
|
||||
}
|
||||
if (alertGui) {
|
||||
// todo: if alertGui is activated on frontend and websocketcontroller, insert emit here
|
||||
|
||||
// todo: if alertGui is activated on frontend and websockets controller, insert emit here
|
||||
if (loggerAlertGui) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* print a debug message if LOG_LEVEL >= LEVEL_DEBUG
|
||||
* @param {string} message
|
||||
* @param {string} context
|
||||
* @param {string=} context
|
||||
* @param {boolean=} alertGui
|
||||
*/
|
||||
debug (message, context, alertGui) {
|
||||
var loggerContext = context;
|
||||
var loggerAlertGui = alertGui;
|
||||
|
||||
if (typeof context === 'undefined') {
|
||||
context = this.context;
|
||||
loggerContext = this.context;
|
||||
}
|
||||
|
||||
if (typeof alertGui === 'undefined') {
|
||||
alertGui = false;
|
||||
loggerAlertGui = false;
|
||||
}
|
||||
if (process.env.LOGGER_LEVEL >= LEVEL_DEBUG) {
|
||||
return this.stdout(message, context, 'DEBUG');
|
||||
|
||||
if (process.env.RS_LOGGER_LEVEL >= LEVEL_DEBUG) {
|
||||
return this.stdout(message, loggerContext, 'DEBUG');
|
||||
}
|
||||
if (alertGui) {
|
||||
// todo: if alertGui is activated on frontend and websocketcontroller, insert emit here
|
||||
|
||||
// todo: if alertGui is activated on frontend and websockets controller, insert emit here
|
||||
if (loggerAlertGui) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,30 +148,38 @@ class Logger {
|
||||
* print a debug message if LOG_LEVEL >= LEVEL_ERROR
|
||||
* sends a string to
|
||||
* @param {string} message
|
||||
* @param {string} context
|
||||
* @param {string=} context
|
||||
* @param {boolean=} alertGui
|
||||
*/
|
||||
error (message, context, alertGui) {
|
||||
var loggerContext = context;
|
||||
var loggerAlertGui = alertGui;
|
||||
|
||||
if (typeof context === 'undefined') {
|
||||
context = this.context;
|
||||
loggerContext = this.context;
|
||||
}
|
||||
|
||||
if (typeof alertGui === 'undefined') {
|
||||
alertGui = false;
|
||||
loggerAlertGui = false;
|
||||
}
|
||||
if (process.env.LOGGER_LEVEL >= LEVEL_ERROR) {
|
||||
return this.stdout(message, context, 'ERROR');
|
||||
|
||||
if (process.env.RS_LOGGER_LEVEL >= LEVEL_ERROR) {
|
||||
return this.stdout(message, loggerContext, 'ERROR');
|
||||
}
|
||||
if (alertGui) {
|
||||
// todo: if alertGui is activated on frontend and websocketcontroller, insert emit here
|
||||
|
||||
// todo: if alertGui is activated on frontend and websockets controller, insert emit here
|
||||
if (loggerAlertGui) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// define loglevels in logger class
|
||||
// define log levels in logger class
|
||||
Logger.LEVEL_ERROR = LEVEL_ERROR;
|
||||
Logger.LEVEL_WARN = LEVEL_WARN;
|
||||
Logger.LEVEL_INFO = LEVEL_INFO;
|
||||
Logger.LEVEL_DEBUG = LEVEL_DEBUG;
|
||||
|
||||
module.exports = (context)=>{
|
||||
module.exports = (context) => {
|
||||
return new Logger(context);
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
* @copyright 2016 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Q = require('q');
|
||||
const psFind = require('ps-find');
|
||||
@@ -11,7 +12,7 @@ const spawn = require('child_process').spawn;
|
||||
const logger = require('./Logger')('Nginxrtmp');
|
||||
|
||||
/**
|
||||
* class to watch and controll the nginx rtmpserver process
|
||||
* class to watch and control the NGINX RTMP server process
|
||||
*/
|
||||
class Nginxrtmp {
|
||||
|
||||
@@ -30,24 +31,19 @@ class Nginxrtmp {
|
||||
* @returns {string}
|
||||
*/
|
||||
init () {
|
||||
let promise = null;
|
||||
|
||||
promise = this.getState()
|
||||
.then((state)=>{
|
||||
switch(state) {
|
||||
|
||||
return this.getState()
|
||||
.then((state) => {
|
||||
switch (state) {
|
||||
case 'not_running':
|
||||
this.start();
|
||||
break;
|
||||
|
||||
case 'running':
|
||||
this.logger.info('NGINX allready started...');
|
||||
this.logger.info('NGINX already started');
|
||||
break;
|
||||
default:
|
||||
throw new Error('state could not be detected');
|
||||
throw new Error('NGINX state could not be detected');
|
||||
}
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,15 +82,15 @@ class Nginxrtmp {
|
||||
*/
|
||||
bindNginxProcessEvents (process) {
|
||||
process.stdout.on('data', (data) => {
|
||||
this.logger.info(`The NGINX rtmp process created an output: ${data}`);
|
||||
this.logger.info(`The NGINX RTMP process created an output: ${data}`);
|
||||
});
|
||||
|
||||
process.stderr.on('data', (data) => {
|
||||
this.logger.error(`The NGINX rtmp process created an error output: ${data}`);
|
||||
this.logger.error(`The NGINX RTMP process created an error output: ${data}`);
|
||||
});
|
||||
|
||||
process.stderr.on('close', (code) => {
|
||||
this.logger.error(`The NGINX rtmp process closed with code: ${code}`);
|
||||
this.logger.error(`The NGINX RTMP process exited with code: ${code}`);
|
||||
this.start();
|
||||
});
|
||||
}
|
||||
@@ -110,26 +106,23 @@ class Nginxrtmp {
|
||||
let deferred = Q.defer();
|
||||
|
||||
// delay the state detection if waiting for process is needed
|
||||
Q
|
||||
.delay(delay)
|
||||
Q.delay(delay)
|
||||
.then(()=> {
|
||||
return Q.nfcall(psFind.find, nginxProcessString);
|
||||
})
|
||||
.then(()=>{
|
||||
.then(()=> {
|
||||
state = 'running';
|
||||
})
|
||||
|
||||
// ps-find throws exception in case of 'not found' so we have to handle that
|
||||
.catch(()=>{
|
||||
.catch(()=> { // ps-find throws exception in case of 'not found' so we have to handle that
|
||||
state = 'not_running';
|
||||
})
|
||||
.finally(()=>{
|
||||
.finally(()=> {
|
||||
deferred.resolve(state);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (config)=>{
|
||||
module.exports = (config)=> {
|
||||
return new Nginxrtmp(config);
|
||||
};
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const config = require('../../conf/live.json');
|
||||
const path = require('path');
|
||||
const config = require(path.join(global.__base, 'conf', 'live.json'));
|
||||
const logger = require('./Logger')('Restreamer');
|
||||
const WebsocketsController = require('./WebsocketController');
|
||||
const WebsocketsController = require('./WebsocketsController');
|
||||
const FfmpegCommand = require('fluent-ffmpeg');
|
||||
const Q = require('q');
|
||||
const app = require.main.require('./webserver/app').app;
|
||||
const JsonDB = require('node-json-db');
|
||||
|
||||
/**
|
||||
* class Restreamer creates and manages streams through ffmpeg
|
||||
@@ -31,9 +35,7 @@ class Restreamer {
|
||||
* @returns {string}
|
||||
*/
|
||||
static generateSnapshotPath () {
|
||||
const path = require('path');
|
||||
|
||||
return path.join(__dirname, '..', 'webserver', 'public', 'images', 'live.jpg');
|
||||
return path.join(global.__public, 'images', 'live.jpg');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,17 +43,18 @@ class Restreamer {
|
||||
* @param {boolean} firstSnapshot
|
||||
*/
|
||||
static fetchSnapshot (firstSnapshot) {
|
||||
var command = null;
|
||||
if (Restreamer.data.states.repeatToLocalNginx.type === 'connected' || firstSnapshot) {
|
||||
var command = new FfmpegCommand(Restreamer.generateOutputHLSPath());
|
||||
command = new FfmpegCommand(Restreamer.generateOutputHLSPath());
|
||||
|
||||
command.output(Restreamer.generateSnapshotPath());
|
||||
command.outputOption(config.ffmpeg.options.snapshot);
|
||||
command.on('error', (error)=> {
|
||||
logger.error('Error on fetching snapshot: ' + error.toString());
|
||||
});
|
||||
command.on('end', () =>{
|
||||
command.on('end', () => {
|
||||
logger.info('updated snapshot');
|
||||
Q.delay(process.env.SNAPSHOT_REFRESH_INTERVAL).then(function () {
|
||||
Q.delay(this.calculateSnapshotRefreshInterval()).then(() => {
|
||||
Restreamer.fetchSnapshot(false);
|
||||
});
|
||||
});
|
||||
@@ -59,6 +62,21 @@ class Restreamer {
|
||||
}
|
||||
}
|
||||
|
||||
static calculateSnapshotRefreshInterval () {
|
||||
let fallbackRefreshInterval = 60000;
|
||||
let snapshotRefreshInterval = process.env.RS_SNAPSHOT_REFRESH_INTERVAL.match(/([0-9]+)([a-z]{1,2})?/);
|
||||
|
||||
if (typeof snapshotRefreshInterval[2] === 'undefined' && snapshotRefreshInterval[1] > 30000) {
|
||||
return snapshotRefreshInterval[1];
|
||||
} else if (snapshotRefreshInterval[2] === 'm') {
|
||||
return snapshotRefreshInterval[1] * 1000 * 60;
|
||||
} else if (snapshotRefreshInterval[2] === 's' && snapshotRefreshInterval[1] > 30) {
|
||||
return snapshotRefreshInterval[1] * 1000;
|
||||
}
|
||||
|
||||
return fallbackRefreshInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* stop stream
|
||||
* @param {string} processName
|
||||
@@ -71,47 +89,54 @@ class Restreamer {
|
||||
if (processHasBeenSpawned) {
|
||||
Restreamer.data.processes[processName].kill();
|
||||
Restreamer.data.processes[processName] = {
|
||||
state: 'not_connected'
|
||||
'state': 'not_connected'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* restore the ffmpeg processes from jsondb (called on app start to restore ffmpeg processes
|
||||
* after the applicatoin has been killed or stuff
|
||||
* after the application has been killed or stuff
|
||||
*/
|
||||
static restoreFFMpegProcesses () {
|
||||
var JsonDB = require('node-json-db');
|
||||
var db = new JsonDB(config.jsondb, true, false);
|
||||
var repeatToLocalNginxIsConnected = false;
|
||||
var repeatToLocalNginxIsConnecting = false;
|
||||
var repeatToOptionalOutputIsConnected = false;
|
||||
var repeatToOptionalOutputIsConnecting = false;
|
||||
|
||||
try {
|
||||
Restreamer.data.addresses = db.getData('/addresses');
|
||||
Restreamer.data.states = db.getData('/states');
|
||||
Restreamer.data.options = db.getData('/options');
|
||||
Restreamer.data.userActions = db.getData('/userActions');
|
||||
|
||||
// check if the srcAddress has been repeated to Local Nginx
|
||||
var repeatToLocalNginxIsConnected = Restreamer.data.states.repeatToLocalNginx.type === 'connected';
|
||||
var repeatToLocalNginxIsConnecting = Restreamer.data.states.repeatToLocalNginx.type === 'connecting';
|
||||
var repeatToOptionalOutputIsConnected = Restreamer.data.states.repeatToOptionalOutput.type === 'connected';
|
||||
var repeatToOptionalOutputIsConnecting = Restreamer.data.states.repeatToOptionalOutput.type === 'connecting';
|
||||
repeatToLocalNginxIsConnected = Restreamer.data.states.repeatToLocalNginx.type === 'connected';
|
||||
repeatToLocalNginxIsConnecting = Restreamer.data.states.repeatToLocalNginx.type === 'connecting';
|
||||
repeatToOptionalOutputIsConnected = Restreamer.data.states.repeatToOptionalOutput.type === 'connected';
|
||||
repeatToOptionalOutputIsConnecting = Restreamer.data.states.repeatToOptionalOutput.type === 'connecting';
|
||||
|
||||
if (Restreamer.data.addresses.srcAddress && (!!repeatToLocalNginxIsConnected || !!repeatToLocalNginxIsConnecting)) {
|
||||
Restreamer.startStream(Restreamer.data.addresses.srcAddress, 'repeatToLocalNginx');
|
||||
// check if the srcAddress has been repeated to Local Nginx
|
||||
if (Restreamer.data.addresses.srcAddress &&
|
||||
(repeatToLocalNginxIsConnected || repeatToLocalNginxIsConnecting)) {
|
||||
Restreamer.startStream(
|
||||
Restreamer.data.addresses.srcAddress,
|
||||
'repeatToLocalNginx'
|
||||
);
|
||||
}
|
||||
if (Restreamer.data.addresses.optionalOutputAddress && (!!repeatToOptionalOutputIsConnected || !!repeatToOptionalOutputIsConnecting)) {
|
||||
Restreamer.startStream(Restreamer.data.addresses.srcAddress, 'repeatToOptionalOutput', Restreamer.data.addresses.optionalOutputAddress);
|
||||
}
|
||||
}
|
||||
catch(error) {
|
||||
logger.error('error restoring ffmpeg process: ' + error);
|
||||
if (Restreamer.data.addresses.optionalOutputAddress &&
|
||||
(repeatToOptionalOutputIsConnected || repeatToOptionalOutputIsConnecting)) {
|
||||
Restreamer.startStream(
|
||||
Restreamer.data.addresses.srcAddress,
|
||||
'repeatToOptionalOutput',
|
||||
Restreamer.data.addresses.optionalOutputAddress
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* write JSON file for persistency
|
||||
* write JSON file for persistence
|
||||
*/
|
||||
static writeToDB () {
|
||||
var JsonDB = require('node-json-db');
|
||||
var db = new JsonDB(config.jsondb, true, false);
|
||||
|
||||
db.push('/', Restreamer.dataForJsonDb());
|
||||
@@ -135,26 +160,32 @@ class Restreamer {
|
||||
* add output to ffmpeg command
|
||||
* @param {FfmpegCommand} ffmpegCommand
|
||||
* @param {string} outputAddress
|
||||
* @return {promise}
|
||||
* @return {Promise}
|
||||
*/
|
||||
static addOutput (ffmpegCommand, outputAddress) {
|
||||
ffmpegCommand.output(outputAddress);
|
||||
return Restreamer.appendOutputOptionFromConfig(ffmpegCommand);
|
||||
}
|
||||
|
||||
static applyOptions (ffmpegCommand) {
|
||||
if (Restreamer.data.options.rtspTcp && Restreamer.data.addresses.srcAddress.indexOf('rtsp') === 0) {
|
||||
ffmpegCommand.inputOptions('-rtsp_transport tcp');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* append the ffmpeg options of the config file to an output
|
||||
* @param {FfmpegCommand} ffmpegCommand
|
||||
* @return {promise}
|
||||
* @return {Promise}
|
||||
*/
|
||||
static appendOutputOptionFromConfig (ffmpegCommand) {
|
||||
var deferred = Q.defer();
|
||||
var ffmpegOptions = [];
|
||||
|
||||
ffmpegCommand.ffprobe(function (err, data) {
|
||||
ffmpegCommand.ffprobe((err, data) => {
|
||||
if (err) {
|
||||
return deferred.reject(err);
|
||||
} else {
|
||||
var ffmpegOptions;
|
||||
}
|
||||
|
||||
if (data.streams.length > 1) {
|
||||
ffmpegOptions = config.ffmpeg.options.native_h264;
|
||||
@@ -167,7 +198,6 @@ class Restreamer {
|
||||
ffmpegCommand.outputOption(option);
|
||||
}
|
||||
return deferred.resolve();
|
||||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
@@ -176,14 +206,14 @@ class Restreamer {
|
||||
* update the state of the stream
|
||||
* @param {string} processName
|
||||
* @param {string} state
|
||||
* @param {string} message
|
||||
* @param {string=} message
|
||||
* @return {string} name of the new state
|
||||
*/
|
||||
static updateState (processName, state, message) {
|
||||
logger.debug(`Update state of "${processName}" from state "${Restreamer.data.states[processName].type}" to state "${state}"`);
|
||||
Restreamer.data.states[processName] = {
|
||||
type: state,
|
||||
message: message
|
||||
'type': state,
|
||||
'message': message
|
||||
};
|
||||
Restreamer.writeToDB();
|
||||
Restreamer.updateStreamDataOnGui();
|
||||
@@ -191,38 +221,48 @@ class Restreamer {
|
||||
}
|
||||
|
||||
/**
|
||||
* update the last submitted useraction (like click on stop stream, click on start stream)
|
||||
* update the last submitted user action (like click on stop stream, click on start stream)
|
||||
* @param {string} processName
|
||||
* @param {string} action useraction
|
||||
* @param {string} action user action
|
||||
* @return {string} name of the new user action
|
||||
*/
|
||||
static updateUserAction (processName, action) {
|
||||
logger.debug(`Set useraction of "${processName}" from "${Restreamer.data.userActions[processName]}" to "${action}"`);
|
||||
logger.debug(`Set user action of "${processName}" from "${Restreamer.data.userActions[processName]}" to "${action}"`);
|
||||
Restreamer.data.userActions[processName] = action;
|
||||
Restreamer.writeToDB();
|
||||
Restreamer.updateStreamDataOnGui();
|
||||
return action;
|
||||
}
|
||||
|
||||
/**
|
||||
* update options
|
||||
* @param {Object} options
|
||||
*/
|
||||
static updateOptions (options) {
|
||||
Restreamer.data.options = options;
|
||||
Restreamer.writeToDB();
|
||||
Restreamer.updateStreamDataOnGui();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} src src-address of the ffmpeg stream
|
||||
* @param {string} streamType repeatToOptionalOutput or repeatToLocalNginx
|
||||
* @param {string} optionalOutput address of the optional output
|
||||
* @param {string} retryCounter current value of the retry counter (startStream retries automatically if anything fails)
|
||||
* @param {string=} optionalOutput address of the optional output
|
||||
* @param {string=} retryCounter current value of the retry counter (startStream retries automatically if anything fails)
|
||||
*/
|
||||
static startStream (src, streamType, optionalOutput, retryCounter) {
|
||||
/** @var {FfmpegCommand} instance of FFmpeg command of module fluent-ffmpeg */
|
||||
var command = null;
|
||||
|
||||
/** @var {Promise} Promise to make sure, the add output process has been finished */
|
||||
var addOutputPromise = null;
|
||||
|
||||
logger.info(`Start stream "${streamType}"`);
|
||||
|
||||
// update the retry counter for the streamType
|
||||
Restreamer.data.retryCounter[streamType].current = typeof retryCounter === 'undefined' ? 0 : retryCounter;
|
||||
|
||||
/** @var {FfmpegCommand} instance of FFmpeg command of module fluent-ffmpeg*/
|
||||
var command;
|
||||
|
||||
/** @var {Promise} Promise to make sure, the add output process has been finished */
|
||||
var addOutputPromise;
|
||||
|
||||
/** @var {Boolean} */
|
||||
const repeatToLocalNginx = streamType === 'repeatToLocalNginx';
|
||||
|
||||
@@ -243,36 +283,42 @@ class Restreamer {
|
||||
// repeat to local nginx server
|
||||
if (repeatToLocalNginx) {
|
||||
command = new FfmpegCommand(src, {
|
||||
outputLineLimit: 1
|
||||
'outputLineLimit': 1
|
||||
});
|
||||
|
||||
// add outputs to the ffmpeg stream
|
||||
addOutputPromise = Restreamer.addOutput(command, Restreamer.generateOutputHLSPath())
|
||||
.catch(function (error) {
|
||||
addOutputPromise = Restreamer.addOutput(command, Restreamer.generateOutputHLSPath()).catch((error) => {
|
||||
logger.error(`Error adding one or more outputs: ${error.toString}`);
|
||||
});
|
||||
|
||||
// repeat to optional output
|
||||
} else if (repeatToOptionalOutput) {
|
||||
command = new FfmpegCommand(Restreamer.generateOutputHLSPath(), {
|
||||
outputLineLimit: 1
|
||||
'outputLineLimit': 1
|
||||
});
|
||||
Restreamer.data.addresses.optionalOutputAddress = optionalOutput;
|
||||
addOutputPromise = Restreamer.addOutput(command, optionalOutput);
|
||||
}
|
||||
|
||||
// after adding outputs, define events on the new FFmpeg stream
|
||||
addOutputPromise.then(function () {
|
||||
addOutputPromise.then(() => {
|
||||
var progressMethod = (progress) => {
|
||||
if (Restreamer.data.states[streamType].type === 'connecting') {
|
||||
Restreamer.data.retryCounter[streamType].current = 1;
|
||||
Restreamer.updateState(streamType, 'connected');
|
||||
}
|
||||
Restreamer.data.progresses[streamType] = progress;
|
||||
Restreamer.updateProgressOnGui();
|
||||
command.removeAllListeners('progress');
|
||||
};
|
||||
Restreamer.applyOptions(command);
|
||||
command
|
||||
|
||||
/*
|
||||
* STREAMING STARTED
|
||||
*/
|
||||
.on('start', function (commandLine) {
|
||||
// stream started
|
||||
.on('start', (commandLine) => {
|
||||
if (Restreamer.data.userActions[streamType] === 'stop') {
|
||||
logger.debug('Skipping on "start" event of FFmpeg command since "stopped" has been clicked');
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
logger.debug(`FFmpeg spawned: ${commandLine}`);
|
||||
Restreamer.data.processes[streamType] = command;
|
||||
|
||||
@@ -280,13 +326,27 @@ class Restreamer {
|
||||
if (repeatToLocalNginx) {
|
||||
Restreamer.fetchSnapshot(true);
|
||||
}
|
||||
})
|
||||
|
||||
// stream ended
|
||||
.on('end', ()=> {
|
||||
Restreamer.updateState(streamType, 'disconnected');
|
||||
Restreamer.data.retryCounter[streamType].current++;
|
||||
if (Restreamer.data.retryCounter[streamType].current <= config.ffmpeg.monitor.retries) {
|
||||
if (Restreamer.data.userActions[streamType] === 'stop') {
|
||||
logger.debug('Skipping retry since "stopped" has been clicked');
|
||||
return;
|
||||
}
|
||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(() => {
|
||||
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||
});
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* ERROR HANDLER
|
||||
*/
|
||||
.on('error', (error)=>{
|
||||
// stream error handler
|
||||
.on('error', (error) => {
|
||||
if (error.toString().indexOf('SIGKILL') > -1) {
|
||||
Restreamer.updateState(streamType, 'disconnected');
|
||||
logger.info(`FFmpeg streaming stopped for "${streamType}"`);
|
||||
@@ -296,79 +356,47 @@ class Restreamer {
|
||||
if (Restreamer.data.userActions[streamType] === 'stop') {
|
||||
logger.debug('Skipping retry since "stopped" has been clicked');
|
||||
return;
|
||||
}
|
||||
Restreamer.updateState(streamType, 'error', error.toString());
|
||||
logger.error(`Error on stream ${streamType}: ${error.toString()}`);
|
||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(() => {
|
||||
logger.info(`Retry FFmpeg connection to "${src}" retry counter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||
});
|
||||
} else {
|
||||
Restreamer.updateState(streamType, 'error', error.toString());
|
||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait} ms`);
|
||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(function () {
|
||||
logger.info(`Retry FFmpeg connection to "${src}" retrycounter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
STREAMING ENDED
|
||||
*/
|
||||
.on('end', ()=>{
|
||||
Restreamer.updateState(streamType, 'disconnected');
|
||||
Restreamer.data.retryCounter[streamType].current++;
|
||||
if (Restreamer.data.retryCounter[streamType].current <= config.ffmpeg.monitor.retries) {
|
||||
if (Restreamer.data.userActions[streamType] === 'stop') {
|
||||
logger.debug('Skipping retry since "stopped" has been clicked');
|
||||
return;
|
||||
} else {
|
||||
logger.info(`Retrying FFmpeg connection to "${src}" after "${config.ffmpeg.monitor.restart_wait}" ms`);
|
||||
Q.delay(config.ffmpeg.monitor.restart_wait).then(function () {
|
||||
logger.info(`Retry FFmpeg connection to "${src}" retrycounter: ${Restreamer.data.retryCounter[streamType].current}`);
|
||||
Restreamer.startStream(src, streamType, optionalOutput, Restreamer.data.retryCounter[streamType].current);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* PROGRESS
|
||||
*/
|
||||
|
||||
var progressMethod = function (progress) {
|
||||
if (Restreamer.data.states[streamType].type === 'connecting') {
|
||||
Restreamer.updateState(streamType, 'connected');
|
||||
}
|
||||
Restreamer.data.progresses[streamType] = progress;
|
||||
Restreamer.updateProgressOnGui();
|
||||
command.removeAllListeners('progress');
|
||||
};
|
||||
|
||||
command.on('progress', progressMethod);
|
||||
setInterval(()=>{
|
||||
setInterval(()=> {
|
||||
if (command.listeners('progress').length === 0) {
|
||||
command.on('progress', progressMethod);
|
||||
}
|
||||
}, 1000);
|
||||
command.exec();
|
||||
}).catch(function (error) {
|
||||
}).catch((error) => {
|
||||
logger.error(`Error starting FFmpeg command: ${error.toString()}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* binded on app-start to bind websocketevents of Restreamer
|
||||
* bind websocket events on application start
|
||||
*/
|
||||
static bindWebsocketEvents () {
|
||||
WebsocketsController.addOnConnectionEventToNamespace('/', function (socket) {
|
||||
WebsocketsController.addOnConnectionEventToNamespace('/', (socket) => {
|
||||
socket.on('startStream', (options)=> {
|
||||
Restreamer.updateUserAction(options.streamType, 'start');
|
||||
Restreamer.updateOptions(options.options);
|
||||
Restreamer.startStream(options.src, options.streamType, options.optionalOutput);
|
||||
});
|
||||
socket.on('stopStream', (streamType)=>{
|
||||
socket.on('stopStream', (streamType)=> {
|
||||
Restreamer.updateUserAction(streamType, 'stop');
|
||||
Restreamer.stopStream(streamType);
|
||||
});
|
||||
socket.on('checkForAppUpdates', ()=>{
|
||||
const app = require('../webserver/app');
|
||||
|
||||
socket.on('checkForAppUpdates', ()=> {
|
||||
socket.emit('checkForAppUpdatesResult', app.get('updateAvailable'));
|
||||
});
|
||||
socket.on('checkStates', Restreamer.updateStreamDataOnGui);
|
||||
@@ -381,12 +409,13 @@ class Restreamer {
|
||||
* @returns {object}
|
||||
*/
|
||||
static extractDataOfStreams () {
|
||||
var sData = {};
|
||||
sData.userActions = Restreamer.data.userActions;
|
||||
sData.addresses = Restreamer.data.addresses;
|
||||
sData.states = Restreamer.data.states;
|
||||
sData.retryCounter = Restreamer.data.retryCounter;
|
||||
return sData;
|
||||
return {
|
||||
'addresses': Restreamer.data.addresses,
|
||||
'options': Restreamer.data.options,
|
||||
'userActions': Restreamer.data.userActions,
|
||||
'states': Restreamer.data.states,
|
||||
'retryCounter': Restreamer.data.retryCounter
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -394,61 +423,69 @@ class Restreamer {
|
||||
* @return {object}
|
||||
*/
|
||||
static dataForJsonDb () {
|
||||
var dbData = {};
|
||||
dbData.addresses = Restreamer.data.addresses;
|
||||
dbData.userActions = Restreamer.data.userActions;
|
||||
dbData.states = Restreamer.data.states;
|
||||
return dbData;
|
||||
return {
|
||||
'addresses': Restreamer.data.addresses,
|
||||
'options': Restreamer.data.options,
|
||||
'userActions': Restreamer.data.userActions,
|
||||
'states': Restreamer.data.states
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
define data structure of Restreamer Data
|
||||
define data structure of Restreamer Data
|
||||
*/
|
||||
Restreamer.data = {
|
||||
retryCounter: {
|
||||
repeatToLocalNginx: {
|
||||
current: 0,
|
||||
max: config.ffmpeg.monitor.retries
|
||||
'retryCounter': {
|
||||
'repeatToLocalNginx': {
|
||||
'current': 0,
|
||||
'max': config.ffmpeg.monitor.retries
|
||||
},
|
||||
repeatToOptionalOutput: {
|
||||
current: 0,
|
||||
max: config.ffmpeg.monitor.retries
|
||||
'repeatToOptionalOutput': {
|
||||
'current': 0,
|
||||
'max': config.ffmpeg.monitor.retries
|
||||
}
|
||||
},
|
||||
states: {
|
||||
repeatToLocalNginx: {
|
||||
type: 'disconnected',
|
||||
message: ''
|
||||
'options': {
|
||||
'rtspTcp': false
|
||||
},
|
||||
repeatToOptionalOutput: {
|
||||
type: 'disconnected',
|
||||
message: ''
|
||||
'states': {
|
||||
'repeatToLocalNginx': {
|
||||
'type': 'disconnected',
|
||||
'message': ''
|
||||
},
|
||||
'repeatToOptionalOutput': {
|
||||
'type': 'disconnected',
|
||||
'message': ''
|
||||
}
|
||||
},
|
||||
userActions: {
|
||||
repeatToLocalNginx: 'start',
|
||||
repeatToOptionalOutput: 'start'
|
||||
'userActions': {
|
||||
'repeatToLocalNginx': 'start',
|
||||
'repeatToOptionalOutput': 'start'
|
||||
},
|
||||
processes: {
|
||||
'processes': {
|
||||
|
||||
// overwritten with ffmpeg process if stream has been started
|
||||
repeatToLocalNginx: {
|
||||
state: 'not_connected'
|
||||
'repeatToLocalNginx': {
|
||||
'state': 'not_connected'
|
||||
},
|
||||
|
||||
// overwritten with ffmpeg process if stream has been started
|
||||
repeatToOptionalOutput: {
|
||||
state: 'not_connected'
|
||||
'repeatToOptionalOutput': {
|
||||
'state': 'not_connected'
|
||||
}
|
||||
},
|
||||
progresses: {
|
||||
'progresses': {
|
||||
|
||||
// overwritten with ffmpeg process if stream has been started
|
||||
repeatToLocalNginx: {},
|
||||
'repeatToLocalNginx': {},
|
||||
|
||||
// overwritten with ffmpeg process if stream has been started
|
||||
repeatToOptionalOutput: {}
|
||||
'repeatToOptionalOutput': {}
|
||||
},
|
||||
addresses: {
|
||||
srcAddress: '',
|
||||
optionalOutputAddress: ''
|
||||
'addresses': {
|
||||
'srcAddress': '',
|
||||
'optionalOutputAddress': ''
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
85
src/classes/RestreamerData.js
Normal file
85
src/classes/RestreamerData.js
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* @file holds the code for the class EnvVar
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const Q = require('q');
|
||||
const path = require('path');
|
||||
const Validator = require('jsonschema').Validator;
|
||||
|
||||
const logger = require('./Logger')('RestreamerData');
|
||||
|
||||
const dbPath = path.join(global.__base, 'db');
|
||||
const dbFile = 'v1.json';
|
||||
|
||||
const confPath = path.join(global.__base, 'conf');
|
||||
const schemaFile = 'jsondb_v1_schema.json';
|
||||
|
||||
class RestreamerData {
|
||||
|
||||
static checkJSONDb () {
|
||||
var schemadata = {};
|
||||
var dbdata = {};
|
||||
var deferred = Q.defer();
|
||||
var readSchema = Q.nfcall(fs.readFile, path.join(confPath, schemaFile));
|
||||
var readDBFile = Q.nfcall(fs.readFile, path.join(dbPath, dbFile));
|
||||
|
||||
logger.info('Checking jsondb file...');
|
||||
readSchema
|
||||
.then((s) => {
|
||||
schemadata = JSON.parse(s.toString('utf8'));
|
||||
return readDBFile;
|
||||
})
|
||||
.then((d) => {
|
||||
dbdata = JSON.parse(d.toString('utf8'));
|
||||
let v = new Validator();
|
||||
let instance = dbdata;
|
||||
let schema = schemadata;
|
||||
let validateResult = v.validate(instance, schema);
|
||||
|
||||
if (validateResult.errors.length > 0) {
|
||||
logger.debug(`Validation error of v1.db: ${JSON.stringify(validateResult.errors)}`);
|
||||
throw new Error(JSON.stringify(validateResult.errors));
|
||||
} else {
|
||||
logger.debug('"v1.db" is valid');
|
||||
deferred.resolve();
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
var defaultStructure = {
|
||||
'addresses': {
|
||||
'srcAddress': '',
|
||||
'optionalOutputAddress': ''
|
||||
},
|
||||
'options': {
|
||||
'rtspTcp': true
|
||||
},
|
||||
'states': {
|
||||
'repeatToLocalNginx': {
|
||||
'type': 'stopped'
|
||||
},
|
||||
'repeatToOptionalOutput': {
|
||||
'type': 'stopped'
|
||||
}
|
||||
},
|
||||
'userActions': {
|
||||
'repeatToLocalNginx': 'stop',
|
||||
'repeatToOptionalOutput': 'stop'
|
||||
}
|
||||
};
|
||||
logger.debug(`Error reading "v1.db": ${error.toString()}`);
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
fs.mkdirSync(dbPath);
|
||||
}
|
||||
fs.writeFileSync(path.join(dbPath, dbFile), JSON.stringify(defaultStructure));
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RestreamerData;
|
||||
@@ -4,13 +4,16 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const logger = require('../classes/Logger')('WebsocketsController');
|
||||
const logger = require.main.require('./classes/Logger')('WebsocketsController');
|
||||
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
|
||||
* that websocket events are binded, if the websocket server has been initialized (through promise made on app start)
|
||||
* @todo since currently the restream is a singlepage application, there is no need to use different namespaces
|
||||
* 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 {
|
||||
|
||||
@@ -21,10 +24,8 @@ class WebsocketsController {
|
||||
* @param {object} data data to emit to the client event listener
|
||||
*/
|
||||
static emitToNamespace (namespace, event, data) {
|
||||
var app = require('../webserver/app');
|
||||
|
||||
app.get('websocketsReady').promise.then(function (io) {
|
||||
logger.debug('websocket got event ' + event + ' to namespace ' + namespace + '', 'Websockets');
|
||||
app.get('websocketsReady').promise.then((io) => {
|
||||
logger.debug(`websocket got event ${event} to namespace ${namespace}`, 'Websockets');
|
||||
io.of(namespace).emit(event, data);
|
||||
});
|
||||
}
|
||||
@@ -35,29 +36,23 @@ class WebsocketsController {
|
||||
* @param {function} callback
|
||||
*/
|
||||
static addOnConnectionEventToNamespace (namespace, callback) {
|
||||
var app = require('../webserver/app');
|
||||
|
||||
app.get('websocketsReady').promise.then(function (io) {
|
||||
app.get('websocketsReady').promise.then((io) => {
|
||||
var nsp = io.of(namespace);
|
||||
|
||||
nsp.on('connection', function (socket) {
|
||||
nsp.on('connection', (socket) => {
|
||||
callback(socket);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* bind default events of all classes that are using websocketevents
|
||||
* bind default events of all classes that are using websockets events
|
||||
*/
|
||||
static bindDefaultEvents () {
|
||||
WebsocketsController.addOnConnectionEventToNamespace('/', function (socket) {
|
||||
socket.on('getVersion', (options)=> {
|
||||
var packageJson = require('../../package.json');
|
||||
|
||||
WebsocketsController.addOnConnectionEventToNamespace('/', (socket) => {
|
||||
socket.on('getVersion', () => {
|
||||
socket.emit('version', packageJson.version);
|
||||
});
|
||||
var app = require('../webserver/app');
|
||||
|
||||
socket.emit('publicIp', app.get('publicIp'));
|
||||
});
|
||||
require('./Restreamer').bindWebsocketEvents();
|
||||
213
src/start.js
213
src/start.js
@@ -1,23 +1,28 @@
|
||||
/**
|
||||
* @file this file is loaded on application start and inits the application
|
||||
* @file this file is loaded on application start and initializes the application
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
const logger = require('./classes/Logger')('start');
|
||||
const EnvVar = require('./classes/EnvVar');
|
||||
const packageJson = require('../package.json');
|
||||
const app = require('./webserver/app');
|
||||
const config = require('../conf/live.json');
|
||||
const nginxrtmp = require('./classes/Nginxrtmp')(config);
|
||||
const Q = require('q');
|
||||
const Validator = require('jsonschema').Validator;
|
||||
const fs = require('fs');
|
||||
'use strict';
|
||||
const path = require('path');
|
||||
|
||||
/*
|
||||
init simple_streamer with environments
|
||||
*/
|
||||
global.__src = __dirname;
|
||||
global.__base = path.join(__dirname, '..');
|
||||
global.__public = path.join(__dirname, 'webserver', 'public');
|
||||
|
||||
const logger = require('./classes/Logger')('start');
|
||||
const EnvVar = require('./classes/EnvVar');
|
||||
const packageJson = require(path.join('..', 'package.json'));
|
||||
const config = require(path.join(global.__base, 'conf', 'live.json'));
|
||||
const nginxrtmp = require('./classes/Nginxrtmp')(config);
|
||||
const Q = require('q');
|
||||
const Restreamer = require('./classes/Restreamer');
|
||||
const RestreamerData = require('./classes/RestreamerData');
|
||||
const WebsocketsController = require('./classes/WebsocketsController');
|
||||
const restreamerApp = require('./webserver/app');
|
||||
|
||||
// show start message
|
||||
logger.info(' _ _ _ _ ', false);
|
||||
logger.info(' __| | __ _| |_ __ _ _ __| |___ ___(_)', false);
|
||||
logger.info(' / _ |/ _ | __/ _ | __| _ |/ _ | |', false);
|
||||
@@ -27,167 +32,35 @@ logger.info('', false);
|
||||
logger.info('Restreamer v' + packageJson.version, false);
|
||||
logger.info('', false);
|
||||
logger.info('ENVIRONMENTS', false);
|
||||
logger.info('More informations in our Docs', false);
|
||||
logger.info('More information in our Docs', false);
|
||||
logger.info('', false);
|
||||
|
||||
// define environment variables
|
||||
var env_vars = [];
|
||||
// setup environment vars
|
||||
EnvVar.init(config);
|
||||
|
||||
env_vars.push(new EnvVar('NODEJS_PORT', false, 3000, 'Webserver port of application'));
|
||||
env_vars.push(new EnvVar('LOGGER_LEVEL', true, '3', 'Logger level to defined, what should be logged'));
|
||||
env_vars.push(new EnvVar('TIMEZONE', true, 'Europe/Berlin', 'Set the timezone'));
|
||||
env_vars.push(new EnvVar('SNAPSHOT_REFRESH_INTERVAL', false, 60000, 'Interval to create a new Snapshot'));
|
||||
env_vars.push(new EnvVar('CREATE_HEAPDUMPS', false, 'false', 'Create Heapdumps of application'));
|
||||
// check for app updates
|
||||
restreamerApp.checkForRestreamerUpdates();
|
||||
// Check for updates each 12 hours
|
||||
setInterval(restreamerApp.checkForRestreamerUpdates, 12 * 3600 * 1000);
|
||||
|
||||
// manage all environments
|
||||
var killProcess = false;
|
||||
|
||||
for (let e of env_vars) {
|
||||
if (typeof process.env[e.name] !== 'undefined') {
|
||||
logger.info(`ENV "${e.name}=${process.env[e.name]}"`, e.description);
|
||||
} else if (e.required === true) {
|
||||
logger.error(`No value set for env "${e.name}", but it is required`);
|
||||
killProcess = true;
|
||||
} else {
|
||||
process.env[e.name] = e.defaultValue;
|
||||
logger.info(`ENV "${e.name}=${process.env[e.name]}", set to default-value!`, e.description);
|
||||
}
|
||||
}
|
||||
|
||||
// kill process after a short delay to log the error message to console
|
||||
if (killProcess === true) {
|
||||
setTimeout(()=> {
|
||||
process.exit();
|
||||
}, 500);
|
||||
}
|
||||
logger.info('', false);
|
||||
|
||||
/**
|
||||
* check if the data from jsondb is valid against the Restreamer jsondb schema
|
||||
* @returns {promise}
|
||||
*/
|
||||
const checkJsonDb = function () {
|
||||
logger.info('Checking jsondb file...');
|
||||
|
||||
var schemadata;
|
||||
var dbdata;
|
||||
var deferred = Q.defer();
|
||||
|
||||
var readSchema = Q.nfcall(fs.readFile, path.join(__dirname, '../', 'conf', 'jsondb_v1_schema.json'));
|
||||
var readDBFile = Q.nfcall(fs.readFile, path.join(__dirname, '../', 'db', 'v1.json'));
|
||||
|
||||
readSchema
|
||||
.then((s)=> {
|
||||
schemadata = JSON.parse(s.toString('utf8'));
|
||||
return readDBFile;
|
||||
})
|
||||
.then((d)=> {
|
||||
dbdata = JSON.parse(d.toString('utf8'));
|
||||
let v = new Validator();
|
||||
let instance = dbdata;
|
||||
let schema = schemadata;
|
||||
let validateResult = v.validate(instance, schema);
|
||||
|
||||
if (validateResult.errors.length > 0) {
|
||||
logger.debug(`Validation error of v1.db: ${JSON.stringify(validateResult.errors)}`);
|
||||
throw new Error(JSON.stringify(validateResult.errors));
|
||||
} else {
|
||||
logger.debug('"v1.db" is valid');
|
||||
deferred.resolve();
|
||||
}
|
||||
}).catch(function (error) {
|
||||
logger.debug(`Error reading "v1.db": ${error.toString()}`);
|
||||
var defaultStructure = {
|
||||
addresses: {
|
||||
srcAddress: '',
|
||||
optionalOutputAddress: ''
|
||||
},
|
||||
states: {
|
||||
repeatToLocalNginx: {
|
||||
type: 'stopped'
|
||||
},
|
||||
repeatToOptionalOutput: {
|
||||
type: 'stopped'
|
||||
}
|
||||
},
|
||||
userActions: {
|
||||
repeatToLocalNginx: 'stop',
|
||||
repeatToOptionalOutput: 'stop'
|
||||
}
|
||||
};
|
||||
|
||||
fs.writeFileSync(path.join(__dirname, '../', 'db', 'v1.json'), JSON.stringify(defaultStructure));
|
||||
deferred.resolve();
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* start the express webserver
|
||||
* @returns {promise}
|
||||
*/
|
||||
const startWebserver = function startWebserver () {
|
||||
var deferred = Q.defer();
|
||||
|
||||
logger.info('Starting webserver...');
|
||||
app.set('port', process.env.NODEJS_PORT);
|
||||
var server = app.listen(app.get('port'), function () {
|
||||
require('./classes/WebsocketController').bindDefaultEvents();
|
||||
app.set('io', require('socket.io')(server));
|
||||
app.set('server', server.address());
|
||||
|
||||
// promise to determine if the webserver has been started to avoid ws binding before
|
||||
app.get('websocketsReady').resolve(app.get('io'));
|
||||
logger.info(`Webserver running on port ${process.env.NODEJS_PORT}`, 'start.webserver');
|
||||
deferred.resolve(server.address().port);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* get the public ip of the server, on that the Restreamer is running
|
||||
*/
|
||||
const getPublicIp = function () {
|
||||
logger.info('Getting public ip...', 'start.publicip');
|
||||
var exec = require('child_process').exec;
|
||||
|
||||
exec('public-ip', (err, stdout, stderr)=> {
|
||||
if (err) {
|
||||
logger.error(err);
|
||||
}
|
||||
app.set('publicIp', stdout.split('\n')[0]);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Restores FFmpeg processes, that have been spawned i.E. on the last app-start (stored on jsondb)
|
||||
* @returns {promise}
|
||||
*/
|
||||
const restoreFFMPEGProcesses = function () {
|
||||
var deferred = Q.defer();
|
||||
const Restreamer = require('./classes/Restreamer');
|
||||
|
||||
logger.info('Restoring FFmpeg processes...', 'start.restore');
|
||||
Restreamer.restoreFFMpegProcesses();
|
||||
deferred.resolve();
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
/**
|
||||
* let's do it
|
||||
*/
|
||||
// add default websocket events, @todo this will be removed, when the new websocket workflow is implemented
|
||||
WebsocketsController.bindDefaultEvents();
|
||||
|
||||
// start the app
|
||||
nginxrtmp.init()
|
||||
.then(checkJsonDb)
|
||||
.then(startWebserver)
|
||||
.then(checkJsonDb)
|
||||
.then(restoreFFMPEGProcesses)
|
||||
.then(getPublicIp)
|
||||
.catch(function (error) {
|
||||
logger.error(`Error starting webserver and nginx for application: ${error}`);
|
||||
setTimeout(()=> {
|
||||
process.exit();
|
||||
}, 500);
|
||||
.then(()=> {
|
||||
return RestreamerData.checkJSONDb();
|
||||
})
|
||||
.then(()=> {
|
||||
return restreamerApp.startWebserver();
|
||||
})
|
||||
.then(() => {
|
||||
return Q.fcall(Restreamer.restoreFFMpegProcesses);
|
||||
})
|
||||
.then(()=> {
|
||||
restreamerApp.getPublicIp();
|
||||
})
|
||||
.catch((error)=> {
|
||||
let errorMessage = `Error starting webserver and nginx for application: ${error}`;
|
||||
throw new Error(errorMessage);
|
||||
});
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* libraries
|
||||
*/
|
||||
// auth stuff
|
||||
const passport = require('passport');
|
||||
const flash = require('connect-flash');
|
||||
const passportConfig = require('./config/passport');
|
||||
|
||||
// express
|
||||
const express = require('express');
|
||||
@@ -23,81 +21,143 @@ const https = require('https');
|
||||
const path = require('path');
|
||||
const Q = require('q');
|
||||
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 indexRouter = require('./controllers/index');
|
||||
const apiV1 = require('./controllers/api/v1');
|
||||
|
||||
// middleware
|
||||
const expressLogger = require('./middleware/expressLogger');
|
||||
|
||||
/**
|
||||
* Class for the ReStreamer webserver, powered by express.js
|
||||
*/
|
||||
const packageJson = require('../../package.json');
|
||||
const logger = require('../classes/Logger')('Webserver');
|
||||
class RestreamerExpressApp {
|
||||
|
||||
// middlewares
|
||||
const expressLogger = require('./middlewares/expressLogger');
|
||||
/**
|
||||
* constructs a new express app with prod or dev config
|
||||
*/
|
||||
constructor () {
|
||||
this.app = express();
|
||||
this.secretKey = crypto.randomBytes(16).toString('hex');
|
||||
|
||||
// create express app
|
||||
const app = express();
|
||||
if (process.env.RS_NODE_ENV === 'dev') {
|
||||
this.initDev();
|
||||
} else {
|
||||
this.initProd();
|
||||
}
|
||||
}
|
||||
|
||||
// generate random key
|
||||
const secretKey = crypto.randomBytes(16).toString('hex');
|
||||
/**
|
||||
* use sessions for the express app
|
||||
*/
|
||||
useSessions () {
|
||||
this.app.use(session({
|
||||
'secret': this.secretKey,
|
||||
'resave': false,
|
||||
'saveUninitialized': true // session secret
|
||||
}));
|
||||
}
|
||||
|
||||
app.use(session({
|
||||
secret: secretKey,
|
||||
resave: false,
|
||||
/**
|
||||
* use passport auth
|
||||
*/
|
||||
useAuth () {
|
||||
// add passport auth
|
||||
this.app.use(passport.initialize());
|
||||
|
||||
// session secret
|
||||
saveUninitialized: true}));
|
||||
// persistent login sessions
|
||||
this.app.use(passport.session());
|
||||
|
||||
// create promise for 'websockets ready'
|
||||
app.set('websocketsReady', Q.defer());
|
||||
// add config to passport
|
||||
passportConfig(passport);
|
||||
}
|
||||
|
||||
// add passport auth
|
||||
app.use(passport.initialize());
|
||||
|
||||
// use connect-flash for flash messages stored in session
|
||||
app.use(flash());
|
||||
/**
|
||||
* add automatic parsers for the body
|
||||
*/
|
||||
addParsers () {
|
||||
this.app.use(bodyParser.json());
|
||||
this.app.use(bodyParser.urlencoded({'extended': true}));
|
||||
this.app.use(cookieParser());
|
||||
}
|
||||
|
||||
// persistent login sessions
|
||||
app.use(passport.session());
|
||||
require('./config/passport')(passport);
|
||||
/**
|
||||
* add content compression on responses
|
||||
*/
|
||||
addCompression () {
|
||||
this.app.use(compression());
|
||||
}
|
||||
|
||||
// configure express app
|
||||
app.use(bodyParser.json());
|
||||
app.set('views', __dirname + '/views');
|
||||
app.set('view engine', 'jade');
|
||||
app.use(bodyParser.urlencoded({extended: true}));
|
||||
app.use(cookieParser());
|
||||
app.use(compression());
|
||||
app.set('json spaces', 4);
|
||||
/**
|
||||
* add express logger
|
||||
*/
|
||||
addExpressLogger () {
|
||||
this.app.use('/', expressLogger);
|
||||
}
|
||||
|
||||
require('./controllers/index')(app, passport);
|
||||
app.use('/', expressLogger);
|
||||
/**
|
||||
* beautify json response
|
||||
*/
|
||||
beautifyJSONResponse () {
|
||||
this.app.set('json spaces', 4);
|
||||
}
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function (req, res, next) {
|
||||
/**
|
||||
* create a promise to check when websockets are ready for bindings
|
||||
*/
|
||||
createPromiseForWebsockets () {
|
||||
this.app.set('websocketsReady', Q.defer());
|
||||
}
|
||||
|
||||
/**
|
||||
* add the restreamer routes
|
||||
*/
|
||||
addRoutes () {
|
||||
indexRouter(this.app, passport);
|
||||
this.app.use('/v1', apiV1);
|
||||
}
|
||||
|
||||
/**
|
||||
* add 404 error handling on pages, that have not been found
|
||||
*/
|
||||
add404ErrorHandling () {
|
||||
this.app.use((req, res, next) => {
|
||||
var err = new Error('Not Found ' + req.url);
|
||||
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// no stacktraces leaked to user
|
||||
app.use(function (err, req, res, next) {
|
||||
/**
|
||||
* add ability for internal server errors
|
||||
*/
|
||||
add500ErrorHandling () {
|
||||
this.app.use((err, req, res, next) => {
|
||||
logger.error(err);
|
||||
res.status(err.status || 500);
|
||||
res.send({
|
||||
message: err.message,
|
||||
error: {}
|
||||
'message': err.message,
|
||||
'error': {}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var checkForAppUpdates = function () {
|
||||
/**
|
||||
* check for app updates
|
||||
*/
|
||||
checkForRestreamerUpdates () {
|
||||
const url = {'host': 'datarhei.org', 'path': '/apps.json'};
|
||||
|
||||
logger.debug('Checking app for updates...');
|
||||
https.get(url, function (response) {
|
||||
https.get(url, (response)=> {
|
||||
if (response.statusCode === 200) {
|
||||
response.on('data', function (body) {
|
||||
response.on('data', (body)=> {
|
||||
var updateCheck = JSON.parse(body);
|
||||
var updateAvailable = false;
|
||||
|
||||
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})`);
|
||||
@@ -105,19 +165,94 @@ var checkForAppUpdates = function () {
|
||||
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 successfull');
|
||||
app.set('updateAvailable', updateAvailable);
|
||||
logger.info('Checking app for updates successful');
|
||||
this.app.set('updateAvailable', updateAvailable);
|
||||
});
|
||||
} else {
|
||||
logger.info('Update check failed', false);
|
||||
}
|
||||
}).on('error', function () {
|
||||
}).on('error', () => {
|
||||
logger.info('Update check failed', false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// start interval to check for updates
|
||||
checkForAppUpdates();
|
||||
setInterval(checkForAppUpdates, 24 * 60 * 60 * 1000);
|
||||
/**
|
||||
* 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]);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
/**
|
||||
* start the webserver and open the websocket
|
||||
* @returns {*|promise}
|
||||
*/
|
||||
startWebserver () {
|
||||
var deferred = Q.defer();
|
||||
var server = null;
|
||||
|
||||
logger.info('Starting webserver...');
|
||||
this.app.set('port', process.env.RS_NODE_PORT);
|
||||
server = this.app.listen(this.app.get('port'), ()=> {
|
||||
this.app.set('io', require('socket.io')(server));
|
||||
this.app.set('server', server.address());
|
||||
|
||||
// promise to determine if the webserver has been started to avoid ws binding before
|
||||
this.app.get('websocketsReady').resolve(this.app.get('io'));
|
||||
logger.info(`Webserver running on port ${process.env.RS_NODE_PORT}`);
|
||||
deferred.resolve(server.address().port);
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* stuff that have always to be added to the webapp
|
||||
*/
|
||||
initAlways () {
|
||||
this.useSessions();
|
||||
this.useAuth();
|
||||
this.addParsers();
|
||||
this.addCompression();
|
||||
this.addExpressLogger();
|
||||
this.beautifyJSONResponse();
|
||||
this.createPromiseForWebsockets();
|
||||
this.addRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* prod config for the express app
|
||||
*/
|
||||
initProd () {
|
||||
logger.debug('init webserver with PROD environment');
|
||||
this.initAlways();
|
||||
this.app.get('/', (req, res)=> {
|
||||
res.sendFile(path.join(global.__public, 'index.html'));
|
||||
});
|
||||
this.add404ErrorHandling();
|
||||
this.add500ErrorHandling();
|
||||
}
|
||||
|
||||
/**
|
||||
* dev config for the express app
|
||||
*/
|
||||
initDev () {
|
||||
logger.debug('init webserver with DEV environment');
|
||||
this.initAlways();
|
||||
this.app.get('/', (req, res)=> {
|
||||
res.sendFile(path.join(global.__public, 'index-dev.html'));
|
||||
});
|
||||
this.add404ErrorHandling();
|
||||
this.add500ErrorHandling();
|
||||
}
|
||||
}
|
||||
|
||||
const restreamerApp = new RestreamerExpressApp();
|
||||
|
||||
module.exports = restreamerApp;
|
||||
|
||||
@@ -4,55 +4,36 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var LocalStrategy = require('passport-local').Strategy;
|
||||
var auth = require('../../../conf/live.json').auth;
|
||||
|
||||
if (process.env.RESTREAMER_USERNAME) {
|
||||
var username = process.env.RESTREAMER_USERNAME;
|
||||
|
||||
} else {
|
||||
var username = auth.username;
|
||||
|
||||
}
|
||||
|
||||
if (process.env.RESTREAMER_USERNAME) {
|
||||
var password = process.env.RESTREAMER_PASSWORD;
|
||||
|
||||
} else {
|
||||
var password = auth.password;
|
||||
|
||||
}
|
||||
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 (user, done) {
|
||||
passport.serializeUser(function serializeUser (user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
passport.deserializeUser(function (user, done) {
|
||||
passport.deserializeUser(function deserializeUser (user, done) {
|
||||
done(null, user);
|
||||
});
|
||||
passport.use('local-login', new LocalStrategy({
|
||||
usernameField: 'user',
|
||||
passwordField: 'pass',
|
||||
|
||||
// allows us to pass back the entire request to the callback
|
||||
passReqToCallback: true
|
||||
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 (req, user, pass, done) {
|
||||
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
|
||||
*/
|
||||
// WEBSOCKET SECURITY HERE
|
||||
done(null, auth);
|
||||
} else {
|
||||
done(null, false, req.flash('wrong password or wrong user'));
|
||||
done(null, false);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
43
src/webserver/controllers/api/v1.js
Normal file
43
src/webserver/controllers/api/v1.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @file controller for routing from /v1
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const router = new express.Router();
|
||||
|
||||
// TODO: solve the circular dependency problem and place Restreamer require here
|
||||
|
||||
router.get('/states', (req, res) => {
|
||||
const states = require.main.require('./classes/Restreamer').data.states;
|
||||
|
||||
res.json({
|
||||
'repeat_to_local_nginx': states.repeatToLocalNginx,
|
||||
'repeat_to_optional_output': states.repeatToOptionalOutput
|
||||
});
|
||||
});
|
||||
router.get('/progresses', (req, res) => {
|
||||
const progresses = require.main.require('./classes/Restreamer').data.progresses;
|
||||
|
||||
res.json({
|
||||
'repeat_to_local_nginx': {
|
||||
'frames': progresses.repeatToLocalNginx.frames,
|
||||
'current_fps': progresses.repeatToLocalNginx.currentFps,
|
||||
'current_kbps': progresses.repeatToLocalNginx.currentKbps,
|
||||
'target_size': progresses.repeatToLocalNginx.targetSize,
|
||||
'timemark': progresses.repeatToLocalNginx.timemark
|
||||
},
|
||||
'repeat_to_optional_output': {
|
||||
'frames': progresses.repeatToOptionalOutput.frames,
|
||||
'current_fps': progresses.repeatToOptionalOutput.currentFps,
|
||||
'current_kbps': progresses.repeatToOptionalOutput.currentKbps,
|
||||
'target_size': progresses.repeatToOptionalOutput.targetSize,
|
||||
'timemark': progresses.repeatToOptionalOutput.timemark
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
@@ -4,73 +4,34 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
/* eslint no-unused-vars: 0 */
|
||||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const Restreamer = require('../../classes/Restreamer');
|
||||
|
||||
module.exports = (app, passport) => {
|
||||
|
||||
// static paths
|
||||
app.use('/css', express.static(path.join(__dirname, '../', 'public', 'css')));
|
||||
app.use('/libs', express.static(path.join(__dirname, '../', 'public', 'libs')));
|
||||
app.use('/dist', express.static(path.join(__dirname, '../', 'public', 'dist')));
|
||||
app.use('/help', express.static(path.join(__dirname, '../', 'public', 'help')));
|
||||
app.use('/images', express.static(path.join(__dirname, '../', 'public', 'images')));
|
||||
app.use('/locales', express.static(path.join(__dirname, '../', 'public', 'locales')));
|
||||
app.use('/crossdomain.xml', express.static(path.join(__dirname, '../', 'public', 'crossdomain.xml')));
|
||||
app.use('/player.html', express.static(path.join(__dirname, '../', 'public', 'player.html')));
|
||||
app.get('/main.html', (req, res)=>{
|
||||
if (req.isAuthenticated()) {
|
||||
res.sendFile(path.join(__dirname, '../', 'public', 'main.html'));
|
||||
} else {
|
||||
res.sendFile(path.join(__dirname, '../', 'public', 'login.html'));
|
||||
}
|
||||
app.get('/favicon.ico', (req, res) => {
|
||||
res.sendFile(path.join(global.__public, 'images', 'favicon.ico'));
|
||||
});
|
||||
app.get('/', (req, res)=>{
|
||||
res.sendFile(path.join(__dirname, '../', 'public', 'index.html'));
|
||||
|
||||
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 */
|
||||
app.post('/login',
|
||||
passport.authenticate('local-login', {
|
||||
successRedirect: '/',
|
||||
failureRedirect: '/',
|
||||
failureFlash : true
|
||||
'successRedirect': '/',
|
||||
'failureRedirect': '/#/login_invalid'
|
||||
})
|
||||
);
|
||||
app.get('/logout', (req, res)=>{
|
||||
app.get('/logout', (req, res) => {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
// small get.api
|
||||
app.get('/v1/states', (req, res)=>{
|
||||
var states = Restreamer.data.states;
|
||||
|
||||
res.json({
|
||||
repeat_to_local_nginx: states.repeatToLocalNginx,
|
||||
repeat_to_optional_output: states.repeatToOptionalOutput
|
||||
});
|
||||
});
|
||||
app.get('/v1/progresses', (req, res)=>{
|
||||
var progresses = Restreamer.data.progresses;
|
||||
|
||||
res.json({
|
||||
repeat_to_local_nginx: {
|
||||
frames: progresses.repeatToLocalNginx.frames,
|
||||
current_fps: progresses.repeatToLocalNginx.currentFps,
|
||||
current_kbps: progresses.repeatToLocalNginx.currentKbps,
|
||||
target_size: progresses.repeatToLocalNginx.targetSize,
|
||||
timemark: progresses.repeatToLocalNginx.timemark
|
||||
},
|
||||
repeat_to_optional_output: {
|
||||
frames: progresses.repeatToOptionalOutput.frames,
|
||||
current_fps: progresses.repeatToOptionalOutput.currentFps,
|
||||
current_kbps: progresses.repeatToOptionalOutput.currentKbps,
|
||||
target_size: progresses.repeatToOptionalOutput.targetSize,
|
||||
timemark: progresses.repeatToOptionalOutput.timemark
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@@ -4,24 +4,25 @@
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
/* eslint vars-on-top: 0 */
|
||||
|
||||
const Logger = require('../../classes/Logger');
|
||||
const logger = new Logger('webserver');
|
||||
const logger = require.main.require('./classes/Logger')('webserver');
|
||||
|
||||
module.exports = (req, res, next)=>{
|
||||
module.exports = (req, res, next)=> {
|
||||
req._startTime = new Date();
|
||||
var log = () =>{
|
||||
var log = () => {
|
||||
var code = res.statusCode;
|
||||
var len = parseInt(res.getHeader('Content-Length'), 10);
|
||||
var duration = ('new Date' - 'req._startTime');
|
||||
var url = (req.originalUrl || req.url);
|
||||
var method = req.method;
|
||||
|
||||
if (isNaN(len)) {
|
||||
len = '';
|
||||
} else {
|
||||
len = ' - ' + len;
|
||||
}
|
||||
var duration = ('new Date' - 'req._startTime');
|
||||
var url = (req.originalUrl || req.url);
|
||||
var method = req.method;
|
||||
|
||||
logger.debug(method + ' \'' + url + '\' ' + code + ' ' + duration + ' ' + req.ip + ' ' + len, 'Webserver');
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<allow-access-from domain="*" />
|
||||
<allow-access-from domain="*"/>
|
||||
</cross-domain-policy>
|
||||
@@ -1,7 +1,7 @@
|
||||
html, body {
|
||||
height:100%;
|
||||
margin:0;
|
||||
padding:0
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -9,32 +9,29 @@ body {
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
height:100%;
|
||||
display:table;
|
||||
height: 100%;
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
background: url(../images/bg.png) no-repeat center center fixed;
|
||||
background: #3d3d39 url(../images/bg.png) no-repeat fixed center center;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
background-size: cover;
|
||||
background-color: #3d3d39;
|
||||
}
|
||||
|
||||
.container-body {
|
||||
float: none;
|
||||
margin: 0 auto;
|
||||
max-width: 500px;
|
||||
border-color: #2c2c28;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border: 1px solid #2c2c28;
|
||||
border-radius: 10px;
|
||||
padding: 50px;
|
||||
}
|
||||
|
||||
.row-fluid {
|
||||
height: 100%;
|
||||
display:table-cell;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@@ -53,7 +50,7 @@ h1, h2, h3, h4, h5, h6 {
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-shadow: 0px 1px #000;
|
||||
text-shadow: 0 1px #000;
|
||||
font-weight: 100;
|
||||
margin-bottom: 30px;
|
||||
font-size: 46px;
|
||||
@@ -62,9 +59,10 @@ h1 {
|
||||
a {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a:visted {
|
||||
a:visited {
|
||||
color: #fff;
|
||||
text-decoration: none;
|
||||
}
|
||||
@@ -74,7 +72,6 @@ a:hover, a:active, a:focus {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
.locales {
|
||||
color: #60605e;
|
||||
text-decoration: none;
|
||||
@@ -120,9 +117,9 @@ a:hover, a:active, a:focus {
|
||||
color: #373734;
|
||||
}
|
||||
|
||||
.form-control[disabled]{
|
||||
.form-control[disabled] {
|
||||
color: #60605e;
|
||||
font-weight:bold;
|
||||
font-weight: bold;
|
||||
background-color: #373734;
|
||||
border: 2px solid #434341;
|
||||
}
|
||||
@@ -157,14 +154,12 @@ a:hover, a:active, a:focus {
|
||||
background-color: #ac5647;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container .jumbotron, .container-fluid .jumbotron, .jumbotron {
|
||||
padding-top: 18px;
|
||||
padding-bottom: 18px;
|
||||
margin-top: -4px;
|
||||
margin-bottom: 18px;
|
||||
text-align: center;
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
padding: 18px 20px;
|
||||
}
|
||||
|
||||
.player-link {
|
||||
@@ -187,12 +182,13 @@ label {
|
||||
.modal-content {
|
||||
background-color: #373734;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
border-bottom: 0px solid #e5e5e5;
|
||||
border-bottom: 0 solid #e5e5e5;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border-top: 0px solid #e5e5e5;
|
||||
border-top: 0 solid #e5e5e5;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@@ -209,7 +205,7 @@ label {
|
||||
|
||||
pre {
|
||||
display: block;
|
||||
padding: 9.5px;
|
||||
padding: 10px;
|
||||
margin: 0 0 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.42857143;
|
||||
@@ -224,3 +220,23 @@ pre {
|
||||
.jwplayer:focus, .jwplayer:active {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.footer .links a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon16 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.icon14 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: #3daa48;
|
||||
}
|
||||
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 167 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
108
src/webserver/public/index-dev.html
Normal file
108
src/webserver/public/index-dev.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html ng-app="app">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Restreamer">
|
||||
<meta name="author" content="datarhei">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Restreamer</title>
|
||||
|
||||
<link href="/libs/bootstrap/dist/css/bootstrap.css" rel="stylesheet">
|
||||
<link href="/css/restreamer.css" rel="stylesheet">
|
||||
|
||||
<script src="/libs/jquery/dist/jquery.js"></script>
|
||||
<script src="/libs/bootstrap/dist/js/bootstrap.js"></script>
|
||||
<script src="/libs/clappr/dist/clappr.js"></script>
|
||||
|
||||
<!--[if lt IE 9]>
|
||||
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
|
||||
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
|
||||
<![endif]-->
|
||||
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="/libs/angular/angular.js"></script>
|
||||
<script src="/libs/ui-router/release/angular-ui-router.js"></script>
|
||||
<script src="/libs/angular-animate/angular-animate.js"></script>
|
||||
<script src="/libs/angular-bootstrap/ui-bootstrap.js"></script>
|
||||
<script src="/libs/angular-bootstrap/ui-bootstrap-tpls.js"></script>
|
||||
<script src="/libs/angular-translate/angular-translate.js"></script>
|
||||
<script src="/libs/angular-translate-loader-static-files/angular-translate-loader-static-files.js"></script>
|
||||
|
||||
<!-- ANGULAR APP -->
|
||||
<script src="/scripts/App.js"></script>
|
||||
<script src="/scripts/App.Config.js"></script>
|
||||
|
||||
<!-- HEADER MODULE -->
|
||||
<script src="/scripts/Header/HeaderModule.js"></script>
|
||||
<script src="/scripts/Header/HeaderDirective.js"></script>
|
||||
<script src="/scripts/Header/HeaderController.js"></script>
|
||||
|
||||
<!-- MAIN MODULE -->
|
||||
<script src="/scripts/Main/MainModule.js"></script>
|
||||
<script src="/scripts/Main/MainController.js"></script>
|
||||
|
||||
<!-- LOGIN MODULE -->
|
||||
<script src="/scripts/Login/LoginModule.js"></script>
|
||||
<script src="/scripts/Login/LoginController.js"></script>
|
||||
|
||||
<!-- FOOTER MODULE -->
|
||||
<script src="/scripts/Footer/FooterModule.js"></script>
|
||||
<script src="/scripts/Footer/FooterDirective.js"></script>
|
||||
<script src="/scripts/Footer/FooterController.js"></script>
|
||||
|
||||
<!-- STREAMING INTERFACE MODULE -->
|
||||
<script src="/scripts/StreamingInterface/StreamingInterfaceModule.js"></script>
|
||||
<script src="/scripts/StreamingInterface/StreamingStatusController.js"></script>
|
||||
<script src="/scripts/StreamingInterface/StreamingStatusDirective.js"></script>
|
||||
|
||||
<!-- SHARED -->
|
||||
<script src="/scripts/Shared/LoggerService.js"></script>
|
||||
<script src="/scripts/Shared/WebsocketsService.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="container-body">
|
||||
<div header></div>
|
||||
<div id="content" ui-view></div>
|
||||
<hr/>
|
||||
<div footer></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Large modal -->
|
||||
<div id="player-modal" class="modal fade" tabindex="-1" role="dialog">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Player</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<div id="player" class="embed-responsive-item"></div>
|
||||
</div>
|
||||
<h4>{{'player_modal_help_title' | translate}}
|
||||
<a href="{{config.urls.embedPlayerHelp}}" target="_blank" class="green underline">
|
||||
<span class="glyphicon glyphicon-question-sign icon16" aria-hidden="true"></span>
|
||||
</a>
|
||||
</h4>
|
||||
<pre><iframe src="http://{{publicIp}}:{{windowLocationPort}}/player.html" name="restreamer-player" width="800" height="450" scrolling="no" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen="true"></iframe></pre>
|
||||
<pre><img src="http://{{publicIp}}:{{windowLocationPort}}/images/live.jpg" width="800" height="450"></pre>
|
||||
<p>{{'player_modal_help_content' | translate}}
|
||||
<a href="{{config.urls.portForwardingHelp}}" target="_blank" class="underline">
|
||||
<span class="glyphicon glyphicon-question-sign icon14" aria-hidden="true"></span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Restreamer">
|
||||
<meta name="author" content="datarhei">
|
||||
<link rel="icon" href="images/favicon.ico">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
<title>Restreamer</title>
|
||||
|
||||
<link href="/libs/bootstrap/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
@@ -29,30 +29,17 @@
|
||||
<script src="/libs/angular-bootstrap/ui-bootstrap-tpls.min.js"></script>
|
||||
<script src="/libs/angular-translate/angular-translate.min.js"></script>
|
||||
<script src="/libs/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js"></script>
|
||||
<script src="/dist/application.js"></script>
|
||||
<script src="/dist/application.min.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<div class="row-fluid">
|
||||
<div class="container-body">
|
||||
<div ng-controller="languageCtrl">
|
||||
<p class="pull-right">
|
||||
<a href="#" ng-click="switchLanguage('en_US')" class="locales" ng-class="{'active': langIs('en_US')}">EN</a>
|
||||
/
|
||||
<a href="#" ng-click="switchLanguage('de_DE')" class="locales" ng-class="{'active': langIs('de_DE')}">DE</a>
|
||||
</p>
|
||||
</div>
|
||||
<h1>Restreamer</h1>
|
||||
<div id="content" ui-view ></div>
|
||||
<hr />
|
||||
<div id="footer">
|
||||
<span class="version">
|
||||
v{{version}}
|
||||
</span>
|
||||
<a href="https://datarhei.github.io/restreamer/docs/references-updates.html" target="_blank" class="btn btn-xs btn-success ng-binding ng-scope" ng-if="checkForAppUpdatesResult != false">{{'update_btn' | translate}}</a>
|
||||
<p class="pull-right"><a href="https://github.com/datarhei/restreamer/issues/new" target="_blank">{{'issue_tracker' | translate}}</a> <a href="https://github.com/datarhei/restreamer" target="_blank" style="padding-left:10px">{{'project_page' | translate}}</a></p>
|
||||
</div>
|
||||
<div header></div>
|
||||
<div id="content" ui-view></div>
|
||||
<hr/>
|
||||
<div footer></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,17 +49,27 @@
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<h4 class="modal-title">Player</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="embed-responsive embed-responsive-16by9">
|
||||
<div id="player" class="embed-responsive-item"></div>
|
||||
</div>
|
||||
<h4>{{'player_modal_help_titel' | translate}} <a href="https://datarhei.github.io/restreamer/docs/guides-embed-upon-your-website.html" target="_blank" style="color:#3daa48;text-decoration:underline"><span class="glyphicon glyphicon-question-sign" style="font-size: 16px;" aria-hidden="true"></span></a></h4>
|
||||
<h4>{{'player_modal_help_title' | translate}}
|
||||
<a href="{{config.urls.embedPlayerHelp}}" target="_blank" class="green underline">
|
||||
<span class="glyphicon glyphicon-question-sign icon16" aria-hidden="true"></span>
|
||||
</a>
|
||||
</h4>
|
||||
<pre><iframe src="http://{{publicIp}}:{{windowLocationPort}}/player.html" name="restreamer-player" width="800" height="450" scrolling="no" frameborder="0" webkitallowfullscreen="true" mozallowfullscreen="true" allowfullscreen="true"></iframe></pre>
|
||||
<pre><img src="http://{{publicIp}}:{{windowLocationPort}}/images/live.jpg" width="800" height="450"></pre>
|
||||
<p>{{'player_modal_help_content' | translate}} <a href="https://datarhei.github.io/restreamer/wiki/portforwarding.html" target="_blank" style="text-decoration:underline"><span class="glyphicon glyphicon-question-sign" style="font-size: 14px;" aria-hidden="true"></span></a></p>
|
||||
<p>{{'player_modal_help_content' | translate}}
|
||||
<a href="{{config.urls.portForwardingHelp}}" target="_blank" class="underline">
|
||||
<span class="glyphicon glyphicon-question-sign icon14" aria-hidden="true"></span>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,15 +1,18 @@
|
||||
{
|
||||
"help_titel": "Hilfe",
|
||||
"help_title": "Hilfe",
|
||||
"issue_tracker": "Fehler melden",
|
||||
"project_page": "Hilfe",
|
||||
"update_btn": "Update verfügbar",
|
||||
"login_username": "Benutzername",
|
||||
"login_password": "Passwort",
|
||||
"login_invalid": "Benutzername oder Passwort ist ungültig",
|
||||
"login_btn": "Anmelden",
|
||||
"logout": "Ausloggen",
|
||||
"button_start": "Start",
|
||||
"button_stop": "Stop",
|
||||
"input_titel": "RTMP/RTSP Video Quelle",
|
||||
"input_title": "RTMP/RTSP Video Quelle",
|
||||
"input_example": "z.B. rtsp://192.168.57.100/media.amp",
|
||||
"rtsp_tcp": "RTSP über TCP",
|
||||
"process_input_invalid": "Die Stream-Adresse ist nicht valide. Bitte überprüfe die Eingabe",
|
||||
"process_success": "Das Streaming wurde erfolgreich aufgebaut.",
|
||||
"process_failed": "Auf den Stream konnte nicht zugegriffen werden.",
|
||||
@@ -17,7 +20,7 @@
|
||||
"process_init": "Der Streaming-Prozess wird erstellt. Bitte warten...",
|
||||
"output_optional": "Externer RTMP-Streaming-Server",
|
||||
"output_optional_example": "z.b. rtmp://live.youtube.com/channelId",
|
||||
"player_link_titel": "Player öffnen",
|
||||
"player_modal_help_titel": "Der iFrame-Code und ein Preview-Image zur Einbettung in der Webseite:",
|
||||
"player_link_title": "Player öffnen",
|
||||
"player_modal_help_title": "Der iFrame-Code und ein Preview-Image zur Einbettung in der Webseite:",
|
||||
"player_modal_help_content": "Zusätzlich muss der Port aus den Beispielen auf die IP-Adresse des Rechners weitergeleitet werden"
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
{
|
||||
"help_titel": "Help",
|
||||
"help_title": "Help",
|
||||
"issue_tracker": "Issue alert",
|
||||
"project_page": "Help",
|
||||
"update_btn": "Update available",
|
||||
"login_username": "Username",
|
||||
"login_password": "Password",
|
||||
"login_invalid": "Username or Password is invalid",
|
||||
"login_btn": "Login",
|
||||
"logout": "Logout",
|
||||
"button_start": "Start",
|
||||
"button_stop": "Stop",
|
||||
"input_titel": "RTMP/RTSP Video Source",
|
||||
"input_title": "RTMP/RTSP Video Source",
|
||||
"input_example": "e.g. rtsp://192.168.57.100/media.amp",
|
||||
"rtsp_tcp": "RTSP over TCP",
|
||||
"process_input_invalid": "Invalid stream address, please check your input",
|
||||
"process_success": "Streaming is successfully initiated.",
|
||||
"process_failed": "Your stream wasn‘t accessable.",
|
||||
"process_failed": "Your stream wasn't accessible.",
|
||||
"process_failed_retry": "Retry count",
|
||||
"process_init": "Streaming process is initiating. Please wait...",
|
||||
"output_optional": "External RTMP-Streaming-Server",
|
||||
"output_optional_example": "e.g. rtmp://live.youtube.com/channelId",
|
||||
"player_link_titel": "Open player",
|
||||
"player_modal_help_titel": "iFrame code and preview of image to embed in website:",
|
||||
"player_link_title": "Open player",
|
||||
"player_modal_help_title": "iFrame code and preview of image to embed in website:",
|
||||
"player_modal_help_content": "In addition the port of the examples have to be forwarded from the router to IP address of the computer"
|
||||
}
|
||||
15
src/webserver/public/login.html
Normal file
15
src/webserver/public/login.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<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>
|
||||
85
src/webserver/public/main.html
Normal file
85
src/webserver/public/main.html
Normal file
@@ -0,0 +1,85 @@
|
||||
<!--
|
||||
Repeat to local nginx
|
||||
-->
|
||||
<meta ng-init="$root.loggedIn = true">
|
||||
<div class="form-group">
|
||||
<label>
|
||||
{{'input_title' | translate}}
|
||||
<a href="https://datarhei.github.io/restreamer/docs/references-rtmp-rtsp-video-source.html" target="_blank">
|
||||
<span class="glyphicon glyphicon-question-sign icon16" aria-hidden="true"></span>
|
||||
</a>
|
||||
</label>
|
||||
<input type="text" class="form-control input" id="input_uri"
|
||||
placeholder="{{'input_example' | translate}}"
|
||||
ng-model="reStreamerData.addresses.srcAddress"
|
||||
ng-disabled="showStopButton('repeatToLocalNginx')">
|
||||
</div>
|
||||
|
||||
<streaming-status name="repeatToLocalNginx" data="reStreamerData"></streaming-status>
|
||||
|
||||
<div class="form-inline form-group">
|
||||
<div ng-if="reStreamerData.addresses.srcAddress.indexOf('rtsp') === 0 && showStartButton('repeatToLocalNginx')"
|
||||
class="checkbox pull-left">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="reStreamerData.options.rtspTcp"> {{'rtsp_tcp' | translate}}
|
||||
<!-- TODO: add help for RTSP over TCP
|
||||
<a href="{{config.urls.rtspTcpHelp}}" target="_blank">
|
||||
<span class="glyphicon glyphicon-question-sign icon14" aria-hidden="true"></span>
|
||||
</a>
|
||||
-->
|
||||
</label>
|
||||
</div>
|
||||
<div class="player-link pull-left" ng-if="reStreamerData.states.repeatToLocalNginx.type == 'connected'">
|
||||
<a class="underline" ng-click="openPlayer()">
|
||||
{{'player_link_title' | translate}}
|
||||
</a>
|
||||
</div>
|
||||
<button type="button" class="btn btn-success pull-right"
|
||||
ng-if="showStartButton('repeatToLocalNginx')"
|
||||
ng-click="startStream('repeatToLocalNginx')">
|
||||
{{'button_start' | translate}}
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger pull-right"
|
||||
ng-if="showStopButton('repeatToLocalNginx')"
|
||||
ng-click="stopStream('repeatToLocalNginx')">
|
||||
{{'button_stop' | translate}}
|
||||
</button>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<!-- todo: this hr to css border-bottom or something-->
|
||||
<hr/>
|
||||
|
||||
<!--
|
||||
Repeat to optional Output
|
||||
-->
|
||||
<h4>Optional:</h4>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="activateOptionalOutput"> {{'output_optional' | translate}}
|
||||
<a href="{{config.urls.rtmpServerHelp}}" target="_blank">
|
||||
<span class="glyphicon glyphicon-question-sign icon14" aria-hidden="true"></span>
|
||||
</a>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-group" ng-show="activateOptionalOutput === true">
|
||||
<input type="text" class="form-control input" id="output_optional_uri"
|
||||
placeholder="{{'output_optional_example' | translate}}"
|
||||
ng-model="reStreamerData.addresses.optionalOutputAddress"
|
||||
ng-disabled="showStopButton('repeatToOptionalOutput')">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<streaming-status name="repeatToOptionalOutput" data="reStreamerData"></streaming-status>
|
||||
|
||||
<div class="text-right">
|
||||
<div class="btn btn-danger" ng-click="stopStream('repeatToOptionalOutput')"
|
||||
ng-if="showStopButton('repeatToOptionalOutput')">{{'button_stop' | translate}}
|
||||
</div>
|
||||
<div ng-show="activateOptionalOutput === true">
|
||||
<div class="btn btn-success" ng-click="startStream('repeatToOptionalOutput')"
|
||||
ng-if="showStartButton('repeatToOptionalOutput')">{{'button_start' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -12,7 +12,7 @@
|
||||
<script src="/libs/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="/libs/clappr/dist/clappr.min.js"></script>
|
||||
</head>
|
||||
<body style="margin:0px;background-color:#000;">
|
||||
<body style="margin: 0; background-color: #000;">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div>
|
||||
@@ -20,14 +20,15 @@
|
||||
<div id="player" class="embed-responsive-item"></div>
|
||||
</div>
|
||||
<script>
|
||||
var player = new Clappr.Player({
|
||||
source: ("https:" === window.location.protocol ? "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%"
|
||||
var 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',
|
||||
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
||||
'height': '100%',
|
||||
'width': '100%'
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
25
src/webserver/public/scripts/App.Config.js
Normal file
25
src/webserver/public/scripts/App.Config.js
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
// Global config for frontend
|
||||
const config = {
|
||||
'urls': {
|
||||
// project
|
||||
'issueTracker': 'https://github.com/datarhei/restreamer/issues/new',
|
||||
'projectPage': 'https://github.com/datarhei/restreamer',
|
||||
'updatePage': 'https://datarhei.github.io/restreamer/docs/references-updates.html',
|
||||
|
||||
// help
|
||||
'embedPlayerHelp': 'https://datarhei.github.io/restreamer/docs/guides-embed-upon-your-website.html',
|
||||
'portForwardingHelp': 'https://datarhei.github.io/restreamer/wiki/portforwarding.html',
|
||||
'rtmpServerHelp': 'https://datarhei.github.io/restreamer/docs/references-external-rtmp-streaming-server.html'
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
window.angular.module('app').constant('config', config);
|
||||
38
src/webserver/public/scripts/App.js
Executable file
38
src/webserver/public/scripts/App.js
Executable file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var app = window.angular.module('app', [
|
||||
'ui.router',
|
||||
'ui.bootstrap',
|
||||
'pascalprecht.translate',
|
||||
'Footer',
|
||||
'Header',
|
||||
'Main',
|
||||
'StreamingInterface',
|
||||
'Login']);
|
||||
|
||||
|
||||
app.config(($stateProvider, $urlRouterProvider) => {
|
||||
$urlRouterProvider.otherwise('/');
|
||||
$stateProvider
|
||||
.state('main', {
|
||||
'templateUrl': 'main.html',
|
||||
'url': '/:error',
|
||||
'controller': 'mainController'
|
||||
|
||||
})
|
||||
.state('helpSource', {
|
||||
'templateUrl': 'help/source.html',
|
||||
'url': '/help/source'
|
||||
})
|
||||
.state('helpOptionalOutput', {
|
||||
'templateUrl': 'help/optionalOutput.html',
|
||||
'url': '/help/optionalOutput'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
14
src/webserver/public/scripts/Footer/FooterController.js
Executable file
14
src/webserver/public/scripts/Footer/FooterController.js
Executable file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Footer').controller('footerController', ['ws', '$scope', 'config', (ws, $scope, config) => {
|
||||
ws.emit('getVersion');
|
||||
ws.on('version', (version) => {
|
||||
$scope.version = version;
|
||||
$scope.config = config;
|
||||
});
|
||||
}]);
|
||||
15
src/webserver/public/scripts/Footer/FooterDirective.js
Executable file
15
src/webserver/public/scripts/Footer/FooterDirective.js
Executable file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Footer').directive('footer', () => {
|
||||
return {
|
||||
'restrict': 'A',
|
||||
'replace': true,
|
||||
'templateUrl': '/scripts/Footer/_footer.html',
|
||||
'controller': 'footerController'
|
||||
};
|
||||
});
|
||||
8
src/webserver/public/scripts/Footer/FooterModule.js
Executable file
8
src/webserver/public/scripts/Footer/FooterModule.js
Executable file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Footer', []);
|
||||
14
src/webserver/public/scripts/Footer/_footer.html
Executable file
14
src/webserver/public/scripts/Footer/_footer.html
Executable file
@@ -0,0 +1,14 @@
|
||||
<div class="footer">
|
||||
<span class="version">
|
||||
v{{version}}
|
||||
</span>
|
||||
<a class="btn btn-xs btn-success ng-binding ng-scope"
|
||||
ng-if="checkForAppUpdatesResult != false" href="{{config.urls.updatePage}}" target="_blank">
|
||||
{{'update_btn' | translate}}
|
||||
</a>
|
||||
<p class="pull-right links">
|
||||
<a href="{{config.urls.issueTracker}}" target="_blank">{{'issue_tracker' | translate}}</a>
|
||||
<a href="{{config.urls.projectPage}}" target="_blank">{{'project_page' | translate}}</a>
|
||||
<a ng-if="loggedIn" href="/logout">{{'logout' | translate}}</a>
|
||||
</p>
|
||||
</div>
|
||||
23
src/webserver/public/scripts/Header/HeaderController.js
Executable file
23
src/webserver/public/scripts/Header/HeaderController.js
Executable file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Header').controller('headerController', ['$scope', '$translate', 'loggerService', ($scope, $translate, loggerService) => {
|
||||
$scope.currentLocale = $translate.preferredLanguage();
|
||||
$scope.switchLanguage = (locale) => {
|
||||
$scope.currentLocale = locale;
|
||||
$translate.use(locale).then(
|
||||
() => {
|
||||
loggerService.info('Switched language to ' + locale);
|
||||
},
|
||||
(error) => {
|
||||
loggerService.error('INFO', 'Switching language to ' + locale + ' failed: ' + error);
|
||||
});
|
||||
};
|
||||
$scope.langIs = function langIs (locale) {
|
||||
return locale === $scope.currentLocale;
|
||||
};
|
||||
}]);
|
||||
15
src/webserver/public/scripts/Header/HeaderDirective.js
Executable file
15
src/webserver/public/scripts/Header/HeaderDirective.js
Executable file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Header').directive('header', () => {
|
||||
return {
|
||||
'restrict': 'A',
|
||||
'replace': true,
|
||||
'templateUrl': '/scripts/Header/_header.html',
|
||||
'controller': 'headerController'
|
||||
};
|
||||
});
|
||||
17
src/webserver/public/scripts/Header/HeaderModule.js
Executable file
17
src/webserver/public/scripts/Header/HeaderModule.js
Executable file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Header', []);
|
||||
|
||||
window.angular.module('Header').config(['$translateProvider', ($translateProvider) => {
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
'prefix': 'locales/lang-',
|
||||
'suffix': '.json'
|
||||
});
|
||||
$translateProvider.useSanitizeValueStrategy('escape');
|
||||
$translateProvider.preferredLanguage('en_US');
|
||||
}]);
|
||||
9
src/webserver/public/scripts/Header/_header.html
Executable file
9
src/webserver/public/scripts/Header/_header.html
Executable file
@@ -0,0 +1,9 @@
|
||||
<div>
|
||||
<p class="pull-right">
|
||||
<a ng-click="switchLanguage('en_US')" class="locales" ng-class="{'active': langIs('en_US')}">EN</a>
|
||||
/
|
||||
<a ng-click="switchLanguage('de_DE')" class="locales" ng-class="{'active': langIs('de_DE')}">DE</a>
|
||||
</p>
|
||||
<h1>Restreamer</h1>
|
||||
</div>
|
||||
|
||||
8
src/webserver/public/scripts/Login/LoginController.js
Executable file
8
src/webserver/public/scripts/Login/LoginController.js
Executable file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Login').controller('loginController', [], () => {});
|
||||
9
src/webserver/public/scripts/Login/LoginModule.js
Executable file
9
src/webserver/public/scripts/Login/LoginModule.js
Executable file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// create new angular module
|
||||
window.angular.module('Login', []);
|
||||
157
src/webserver/public/scripts/Main/MainController.js
Executable file
157
src/webserver/public/scripts/Main/MainController.js
Executable file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* @file holds the Angularjs mainController
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Main').controller('mainController',
|
||||
['ws', '$scope', '$location', '$rootScope', '$stateParams', 'config',
|
||||
function mainController (ws, $scope, $location, $rootScope, $stateParams, config) {
|
||||
let setup = false;
|
||||
let player = null;
|
||||
|
||||
if ($stateParams.error === 'login_invalid') {
|
||||
$scope.login_invalid = 'login_invalid';
|
||||
}
|
||||
|
||||
$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',
|
||||
'mediacontrol': {'seekbar': '#3daa48', 'buttons': '#3daa48'},
|
||||
'height': '100%',
|
||||
'width': '100%'
|
||||
});
|
||||
};
|
||||
|
||||
$rootScope.loggedIn = false;
|
||||
|
||||
$scope.optionalOutputInputInvalid = false;
|
||||
$scope.nginxRepeatStreamInputInvalid = false;
|
||||
|
||||
$scope.reStreamerData = {
|
||||
'retryCounter': {
|
||||
'repeatToLocalNginx': 0,
|
||||
'repeatToOptionalOutput': 0
|
||||
},
|
||||
'options': {
|
||||
'rtspTcp': false
|
||||
},
|
||||
'states': {
|
||||
'repeatToLocalNginx': {
|
||||
'type': ''
|
||||
},
|
||||
'repeatToOptionalOutput': {
|
||||
'type': ''
|
||||
}
|
||||
},
|
||||
'userActions': {
|
||||
'repeatToLocalNginx': '',
|
||||
'repeatToOptionalOutput': ''
|
||||
},
|
||||
'progresses': {
|
||||
'repeatToLocalNginx': '',
|
||||
'repeatToOptionalOutput': ''
|
||||
},
|
||||
'addresses': {
|
||||
'optionalOutputAddress': '',
|
||||
'srcAddress': ''
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.windowLocationPort = $location.port();
|
||||
|
||||
$scope.optionalOutput = '';
|
||||
|
||||
$scope.showStopButton = (streamType) => {
|
||||
return $scope.reStreamerData.userActions[streamType] === 'start';
|
||||
};
|
||||
|
||||
$scope.showStartButton = (streamType) => {
|
||||
return $scope.reStreamerData.userActions[streamType] === 'stop';
|
||||
};
|
||||
|
||||
$scope.openPlayer = () => {
|
||||
if (player === null) {
|
||||
initClappr();
|
||||
}
|
||||
$('#player-modal').modal('show').on('hide.bs.modal', function closeModal (e) {
|
||||
player.stop();
|
||||
$(this).off('hide.bs.modal');
|
||||
$(this).modal('hide');
|
||||
return e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Configure Websockets
|
||||
*/
|
||||
|
||||
// check states of hls and rtmp stream
|
||||
ws.emit('checkStates');
|
||||
|
||||
// check for app updates
|
||||
ws.emit('checkForAppUpdates');
|
||||
|
||||
// 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) => {
|
||||
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 = '';
|
||||
|
||||
if ($scope.activateOptionalOutput === true) {
|
||||
optionalOutput = $scope.reStreamerData.addresses.optionalOutputAddress;
|
||||
}
|
||||
|
||||
if (streamType === 'repeatToOptionalOutput') {
|
||||
$scope.optionalOutputInputInvalid = !rtmpRegex.test(optionalOutput);
|
||||
if ($scope.optionalOutputInputInvalid) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$scope.nginxRepeatStreamInputInvalid = !rtmpRegex.test($scope.reStreamerData.addresses.srcAddress);
|
||||
if ($scope.nginxRepeatStreamInputInvalid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ws.emit('startStream', {
|
||||
'src': $scope.reStreamerData.addresses.srcAddress,
|
||||
'options': $scope.reStreamerData.options,
|
||||
'streamType': streamType,
|
||||
'optionalOutput': optionalOutput
|
||||
});
|
||||
};
|
||||
|
||||
$scope.stopStream = (streamType) => {
|
||||
ws.emit('stopStream', streamType);
|
||||
};
|
||||
}]);
|
||||
8
src/webserver/public/scripts/Main/MainModule.js
Executable file
8
src/webserver/public/scripts/Main/MainModule.js
Executable file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('Main', []);
|
||||
80
src/webserver/public/scripts/factories/loggerService.js → src/webserver/public/scripts/Shared/LoggerService.js
Normal file → Executable file
80
src/webserver/public/scripts/factories/loggerService.js → src/webserver/public/scripts/Shared/LoggerService.js
Normal file → Executable file
@@ -4,9 +4,10 @@
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
styles of the logging outputs
|
||||
*/
|
||||
/* eslint no-console: 0*/
|
||||
'use strict';
|
||||
|
||||
// styles of the logging output
|
||||
const INFO = 'color: #0000FF; font-weight: bold';
|
||||
const DEBUG = 'color: #AABBCC; font-weight: bold';
|
||||
const ERROR = 'color: #FF0011d; font-weight: bold';
|
||||
@@ -14,74 +15,67 @@ const WEBSOCKETS_IN = 'color: #00BFFF; font-weight: bold';
|
||||
const WEBSOCKETS_OUT = 'color: #00BF00; font-weight: bold';
|
||||
const WEBSOCKETS_NAMESPACE = 'color: #00BF00; font-weight: bold';
|
||||
|
||||
class loggerService {
|
||||
|
||||
/*
|
||||
* no further dependencies needed
|
||||
*/
|
||||
constructor () {}
|
||||
|
||||
/*
|
||||
const LoggerService = function loggerService () {
|
||||
/**
|
||||
* log an info message
|
||||
* @param {string} message
|
||||
*/
|
||||
info (message) {
|
||||
this.info = (message) => {
|
||||
this.log(INFO, message, 'INFO');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* log an info message
|
||||
/**
|
||||
* log an debug message
|
||||
* @param {string} message
|
||||
*/
|
||||
debug (message) {
|
||||
this.debug = (message) => {
|
||||
this.log(DEBUG, message, 'DEBUG');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* log an info message
|
||||
/**
|
||||
* log an error message
|
||||
* @param {string} message
|
||||
*/
|
||||
error (message) {
|
||||
this.error = (message) => {
|
||||
this.log(ERROR, message, 'ERROR');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* log an info message
|
||||
/**
|
||||
* log an websocket in message
|
||||
* @param {string} message
|
||||
*/
|
||||
websockets_in (message) {
|
||||
this.websocketsIn = (message) => {
|
||||
this.log(WEBSOCKETS_IN, message, 'WS_IN');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* log an info message
|
||||
/**
|
||||
* log an websocket out message
|
||||
* @param {string} message
|
||||
*/
|
||||
websockets_out (message) {
|
||||
this.websocketsOut = (message) => {
|
||||
this.log(WEBSOCKETS_OUT, message, 'WS_OUT');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* log an info message
|
||||
/**
|
||||
* log an websocket namespace message
|
||||
* @param {string} message
|
||||
*/
|
||||
websockets_namespace (message) {
|
||||
this.websocketsNamespace = (message) => {
|
||||
this.log(WEBSOCKETS_NAMESPACE, message, 'WS_CONNECT');
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
/**
|
||||
* log a message with style
|
||||
* @param {string} style
|
||||
* @param {string} message
|
||||
* @param {string} type
|
||||
*/
|
||||
log (style, message, type) {
|
||||
console.log('%c ' + '[' + type + ']' + message, style);
|
||||
}
|
||||
}
|
||||
this.log = (style, message, type) => {
|
||||
console.log('%c [' + type + ']' + message, style);
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* configure loggerService as angulerjs Service
|
||||
*/
|
||||
window.app.factory('loggerService', function () {
|
||||
return new loggerService();
|
||||
// configure loggerService as AngularJS Service
|
||||
window.angular.module('app').factory('loggerService', () => {
|
||||
return new LoggerService();
|
||||
});
|
||||
61
src/webserver/public/scripts/Shared/WebsocketsService.js
Executable file
61
src/webserver/public/scripts/Shared/WebsocketsService.js
Executable file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @file Service to handle websocket connections and events
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint no-undef: 0*/
|
||||
'use strict';
|
||||
|
||||
const WebsocketsService = function websockeService ($rootScope, loggerService) {
|
||||
this.$rootScope = $rootScope;
|
||||
this.loggerService = loggerService;
|
||||
this.socket = io.connect();
|
||||
this.loggerService.websocketsNamespace('websockets connected');
|
||||
|
||||
|
||||
/**
|
||||
* emit an event to socket
|
||||
* @param event
|
||||
* @param data
|
||||
* @returns {WebsocketsService}
|
||||
*/
|
||||
this.emit = (event, data) => {
|
||||
this.loggerService.websocketsOut(`emit event "${event}"`);
|
||||
this.socket.emit(event, data);
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* react on an event to socket with callback
|
||||
* @param event
|
||||
* @param {function} callback
|
||||
* @returns {WebsocketsService}
|
||||
*/
|
||||
this.on = (event, callback) => {
|
||||
var self = this;
|
||||
this.loggerService.websocketsIn(`got event "${event}"`);
|
||||
this.socket.on(event, function woEvent () {
|
||||
var args = arguments;
|
||||
self.$rootScope.$apply(function weApply () {
|
||||
callback.apply(null, args);
|
||||
});
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* disable an event on socket
|
||||
* @param event
|
||||
* @param callback
|
||||
*/
|
||||
this.off = (event, callback) => {
|
||||
this.socket.removeListener(event, callback);
|
||||
};
|
||||
};
|
||||
|
||||
// connect service to angular.js
|
||||
window.angular.module('app').factory('ws', ['$rootScope', 'loggerService', ($rootScope, loggerService) => {
|
||||
return new WebsocketsService($rootScope, loggerService);
|
||||
}]);
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('StreamingInterface', []);
|
||||
62
src/webserver/public/scripts/StreamingInterface/StreamingStatusController.js
Executable file
62
src/webserver/public/scripts/StreamingInterface/StreamingStatusController.js
Executable file
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Streaming Status Controller
|
||||
*
|
||||
* controlls the display of the streaming status (fps, status)
|
||||
*/
|
||||
window.angular.module('StreamingInterface').controller('streamingStatusController',
|
||||
['$scope', ($scope) => {
|
||||
/**
|
||||
* extract statusName
|
||||
* @returns {string}
|
||||
*/
|
||||
const statusName = () => {
|
||||
return $scope.data.states[$scope.name].type;
|
||||
};
|
||||
|
||||
/**
|
||||
* check if the status is connecting
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.connecting = () => {
|
||||
return statusName() === 'connecting';
|
||||
};
|
||||
|
||||
/**
|
||||
* check if the status is connected
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.connected = () => {
|
||||
return statusName() === 'connected';
|
||||
};
|
||||
|
||||
/**
|
||||
* check if the status is error
|
||||
* @returns {boolean}
|
||||
*/
|
||||
$scope.error = () => {
|
||||
$scope.retries = $scope.data.retryCounter[$scope.name].current;
|
||||
$scope.maxRetries = $scope.data.retryCounter[$scope.name].max;
|
||||
return statusName() === 'error';
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {number} current FPS
|
||||
*/
|
||||
$scope.fps = () => {
|
||||
return $scope.data.progresses ? $scope.data.progresses[$scope.name].currentFps : 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {number} current bit rate
|
||||
*/
|
||||
$scope.kbps = () => {
|
||||
return $scope.data.progresses ? $scope.data.progresses[$scope.name].currentKbps : 0;
|
||||
};
|
||||
}]);
|
||||
19
src/webserver/public/scripts/StreamingInterface/StreamingStatusDirective.js
Executable file
19
src/webserver/public/scripts/StreamingInterface/StreamingStatusDirective.js
Executable file
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
window.angular.module('StreamingInterface').directive('streamingStatus', () => {
|
||||
return {
|
||||
'scope': {
|
||||
'data': '=',
|
||||
'name': '@name'
|
||||
},
|
||||
'restrict': 'E',
|
||||
'replace': true,
|
||||
'templateUrl': '/scripts/StreamingInterface/_streamingStatus.html',
|
||||
'controller': 'streamingStatusController'
|
||||
};
|
||||
});
|
||||
30
src/webserver/public/scripts/StreamingInterface/_streamingStatus.html
Executable file
30
src/webserver/public/scripts/StreamingInterface/_streamingStatus.html
Executable file
@@ -0,0 +1,30 @@
|
||||
<div class="streamingStatus">
|
||||
<!-- Stream is connecting -->
|
||||
<div class="jumbotron progress-bar-info progress-bar-striped" ng-if="connecting()">
|
||||
{{'process_init' | translate}}
|
||||
</div>
|
||||
|
||||
<!-- Stream is connected -->
|
||||
<div class="jumbotron progress-bar-success progress-bar-striped" ng-if="connected()">
|
||||
{{'process_success' | translate}}
|
||||
<div ng-if="fps()">
|
||||
<span class="ffmpeg-progress fps">{{fps()}} FPS</span>
|
||||
/
|
||||
<span class="ffmpeg-progress kbps">{{kbps()}} Kb/s</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stream has an error -->
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-if="error()">
|
||||
<span>{{'process_failed' | translate}}</span>
|
||||
<span ng-if="maxRetries >= retries">{{'process_failed_retry' | translate}}</span>
|
||||
<span ng-if="maxRetries >= retries">{{retries}} / {{maxRetries}}</span>
|
||||
</div>
|
||||
|
||||
<!-- regex error on input field -->
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-show="nginxRepeatStreamInputInvalid">
|
||||
{{'process_input_invalid' | translate}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
var app = angular.module('app', [ 'ui.router', 'ui.bootstrap', 'pascalprecht.translate' ]);
|
||||
|
||||
app.config(function ($translateProvider) {
|
||||
|
||||
// lang
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: 'locales/lang-',
|
||||
suffix: '.json'
|
||||
});
|
||||
|
||||
$translateProvider.useSanitizeValueStrategy('escape');
|
||||
$translateProvider.preferredLanguage('en_US');
|
||||
});
|
||||
|
||||
app.config(function ($stateProvider, $urlRouterProvider) {
|
||||
|
||||
// For any unmatched url, redirect to /
|
||||
$urlRouterProvider.otherwise('/');
|
||||
|
||||
$stateProvider
|
||||
.state('main', {
|
||||
templateUrl: 'main.html',
|
||||
url: '/',
|
||||
controller: 'mainCtrl'}
|
||||
)
|
||||
.state('helpSource', {
|
||||
templateUrl: 'help/source.html',
|
||||
url: '/help/source'}
|
||||
)
|
||||
.state('helpOptionalOutput', {
|
||||
templateUrl: 'help/optionalOutput.html',
|
||||
url: '/help/optionalOutput'}
|
||||
);
|
||||
});
|
||||
|
||||
app.filter('inArray', function ($filter) {
|
||||
return function (list, arrayFilter, element) {
|
||||
if (arrayFilter) {
|
||||
return $filter('filter')(list, function (listItem) {
|
||||
return arrayFilter.indexOf(listItem[element]) !== -1;
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
window.app.controller('languageCtrl', [ '$scope', '$translate', function ($scope, $translate) {
|
||||
$scope.currentLocale = $translate.preferredLanguage();
|
||||
$scope.switchLanguage = function (locale) {
|
||||
$scope.currentLocale = locale;
|
||||
$translate.use(locale).then(function () {
|
||||
window.Logger.log('INFO', 'Switched language to ' + locale);
|
||||
}, function (error) {
|
||||
window.Logger.error('INFO', 'Switching language to ' + locale + ' failed: ' + error);
|
||||
});
|
||||
};
|
||||
$scope.langIs = function (locale) {
|
||||
return locale === $scope.currentLocale;
|
||||
};
|
||||
} ]);
|
||||
@@ -1,174 +0,0 @@
|
||||
/**
|
||||
* @file holds the Angularjs mainController
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
window.app.controller('mainCtrl', [ 'ws', '$scope', '$location', '$rootScope', '$translate', function (ws, $scope, $location, $rootScope, $translate) {
|
||||
|
||||
// binding just once
|
||||
var setup = false;
|
||||
var player = null;
|
||||
|
||||
const initClappr = function () {
|
||||
player = new Clappr.Player({
|
||||
source: '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%'
|
||||
});
|
||||
};
|
||||
|
||||
$translate.use('en_US');
|
||||
|
||||
$scope.optionalOutputInputInvalid = false;
|
||||
$scope.nginxRepeatStreamInputInvalid = false;
|
||||
|
||||
$scope.reStreamerData = {
|
||||
retryCounter: {
|
||||
repeatToLocalNginx: 0,
|
||||
repeatToOptionalOutput: 0
|
||||
},
|
||||
states: {
|
||||
repeatToLocalNginx: {
|
||||
type: ''
|
||||
},
|
||||
repeatToOptionalOutput: {
|
||||
type: ''
|
||||
}
|
||||
},
|
||||
userActions: {
|
||||
repeatToLocalNginx: '',
|
||||
repeatToOptionalOutput: ''
|
||||
},
|
||||
addresses: {
|
||||
optionalOutputAddress: '',
|
||||
srcAddress: ''
|
||||
}
|
||||
};
|
||||
|
||||
$rootScope.windowLocationPort = $location.port();
|
||||
|
||||
$scope.optionalOutput = '';
|
||||
|
||||
$scope.showStopButton = function (streamType) {
|
||||
return $scope.reStreamerData.userActions[streamType] === 'start';
|
||||
};
|
||||
|
||||
$scope.showStartButton = function (streamType) {
|
||||
return $scope.reStreamerData.userActions[streamType] === 'stop';
|
||||
};
|
||||
|
||||
$scope.nginxRepeatStreamConnecting = function () {
|
||||
return $scope.reStreamerData.states.repeatToLocalNginx.type === 'connecting';
|
||||
};
|
||||
|
||||
$scope.nginxRepeatStreamConnected = function () {
|
||||
return $scope.reStreamerData.states.repeatToLocalNginx.type === 'connected';
|
||||
};
|
||||
|
||||
$scope.nginxRepeatStreamError = function () {
|
||||
return $scope.reStreamerData.states.repeatToLocalNginx.type === 'error';
|
||||
};
|
||||
|
||||
$scope.optionalOutputConnecting = function () {
|
||||
return $scope.reStreamerData.states.repeatToOptionalOutput.type === 'connecting';
|
||||
};
|
||||
|
||||
$scope.optionalOutputConnected = function () {
|
||||
return $scope.reStreamerData.states.repeatToOptionalOutput.type === 'connected';
|
||||
};
|
||||
|
||||
$scope.optionalOutputError = function () {
|
||||
return $scope.reStreamerData.states.repeatToOptionalOutput.type === 'error';
|
||||
};
|
||||
|
||||
$scope.openPlayer = function () {
|
||||
if (player === null) {
|
||||
initClappr();
|
||||
}
|
||||
$('#player-modal').modal('show');
|
||||
$('#player-modal').on('hide.bs.modal', function (e) {
|
||||
player.stop();
|
||||
$('#player-modal').off('hide.bs.modal');
|
||||
$('#player-modal').modal('hide');
|
||||
return e.preventDefault();
|
||||
});
|
||||
};
|
||||
|
||||
/*
|
||||
* Configure Websockets
|
||||
*/
|
||||
|
||||
ws.emit('getVersion');
|
||||
|
||||
// check states of hls and rtmp stream
|
||||
ws.emit('checkStates');
|
||||
|
||||
// check for app updates
|
||||
ws.emit('checkForAppUpdates');
|
||||
|
||||
// prohibit double binding of events
|
||||
if (!setup) {
|
||||
|
||||
/**
|
||||
* test websockets connection (should print below message to browser console if it works)
|
||||
*/
|
||||
ws.on('version', function (version) {
|
||||
$rootScope.version = version;
|
||||
window.Logger.log('INFO', 'Datarhei ' + version + ' websockets connected');
|
||||
});
|
||||
ws.on('updateProgress', function (progresses) {
|
||||
$scope.reStreamerData.progresses = progresses;
|
||||
});
|
||||
ws.on('publicIp', function (publicIp) {
|
||||
$rootScope.publicIp = publicIp;
|
||||
});
|
||||
ws.on('updateStreamData', function (reStreamerData) {
|
||||
$scope.reStreamerData = reStreamerData;
|
||||
if ($scope.showStopButton('repeatToOptionalOutput')) {
|
||||
|
||||
// checkbox
|
||||
$scope.activateOptionalOutput = true;
|
||||
}
|
||||
});
|
||||
ws.on('checkForAppUpdatesResult', function (result) {
|
||||
$rootScope.checkForAppUpdatesResult = result;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.startStream = function (streamType) {
|
||||
const rtmp_regex = /^(?: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 = false;
|
||||
|
||||
if ($scope.activateOptionalOutput === true) {
|
||||
optionalOutput = $scope.reStreamerData.addresses.optionalOutputAddress;
|
||||
}
|
||||
|
||||
if (streamType === 'repeatToOptionalOutput'){
|
||||
$scope.optionalOutputInputInvalid = !rtmp_regex.test(optionalOutput);
|
||||
if ($scope.optionalOutputInputInvalid ) {
|
||||
return;
|
||||
}
|
||||
}else {
|
||||
$scope.nginxRepeatStreamInputInvalid = !rtmp_regex.test($scope.reStreamerData.addresses.srcAddress);
|
||||
if ($scope.nginxRepeatStreamInputInvalid ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ws.emit('startStream', {
|
||||
src: $scope.reStreamerData.addresses.srcAddress,
|
||||
streamType: streamType,
|
||||
optionalOutput: optionalOutput
|
||||
});
|
||||
};
|
||||
|
||||
$scope.stopStream = function (streamType) {
|
||||
ws.emit('stopStream', streamType);
|
||||
};
|
||||
} ]);
|
||||
@@ -1,71 +0,0 @@
|
||||
/**
|
||||
* @file Service to handle websocket connections and events
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
/* jslint browser: true */
|
||||
|
||||
class WebsocketsService {
|
||||
|
||||
/*
|
||||
* construct the Websockets Service
|
||||
* @param $rootScope
|
||||
* @param loggerService
|
||||
*/
|
||||
constructor ($rootScope, loggerService) {
|
||||
this.$rootScope = $rootScope;
|
||||
this.loggerService = loggerService;
|
||||
this.socket = io.connect();
|
||||
this.loggerService.websockets_namespace('websockets connected');
|
||||
}
|
||||
|
||||
/*
|
||||
* emit an event to socket
|
||||
* @param event
|
||||
* @param data
|
||||
* @returns {WebsocketsService}
|
||||
*/
|
||||
emit (event, data) {
|
||||
this.loggerService.websockets_out(`emit event "${event}"`);
|
||||
this.socket.emit(event, data);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* react on an event to socket with callback
|
||||
* @param event
|
||||
* @param {function} callback
|
||||
* @returns {WebsocketsService}
|
||||
*/
|
||||
on (event, callback) {
|
||||
this.loggerService.websockets_in(`got event "${event}"`);
|
||||
var self = this;
|
||||
|
||||
this.socket.on(event, function () {
|
||||
var args = arguments;
|
||||
|
||||
self.$rootScope.$apply(function () {
|
||||
callback.apply(null, args);
|
||||
});
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* disable an event on socket
|
||||
* @param event
|
||||
* @param callback
|
||||
*/
|
||||
off (event, callback) {
|
||||
this.socket.removeListener(event, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* connect service to angular.js
|
||||
*/
|
||||
window.app.factory('ws', [ '$rootScope', 'loggerService', function ($rootScope, loggerService) {
|
||||
return new WebsocketsService($rootScope, loggerService);
|
||||
} ]);
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @link https://github.com/datarhei/restreamer
|
||||
* @copyright 2015 datarhei.org
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
|
||||
window.Logger = {
|
||||
level: {
|
||||
INFO: 'color: #0000FF; font-weight: bold',
|
||||
DEBUG: 'color: #AABBCC; font-weight: bold',
|
||||
ERROR: 'color: #FF0011d; font-weight: bold',
|
||||
WEBSOCKETS_IN: 'color: #00BFFF; font-weight: bold',
|
||||
WEBSOCKETS_OUT: 'color: #00BF00; font-weight: bold',
|
||||
WEBSOCKETS_NAMESPACE: 'color: #00BF00; font-weight: bold'
|
||||
},
|
||||
log: function (level, message) {
|
||||
console.log('%c ' +'[' + level + '] ' + message, this.level[level]);
|
||||
}
|
||||
};
|
||||
@@ -1,11 +0,0 @@
|
||||
<form action="/login" method="POST">
|
||||
<div class="form-group ng-scope">
|
||||
<input type="text" class="form-control input ng-pristine ng-untouched ng-valid" name="user" id="input_username" placeholder="{{'login_username' | translate}}">
|
||||
</div>
|
||||
<div class="form-group ng-scope">
|
||||
<input type="password" class="form-control input ng-pristine ng-untouched ng-valid" name="pass" id="input_password" placeholder="{{'login_password' | translate}}">
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-success ng-binding ng-scope">{{'login_btn' | translate}}</button>
|
||||
</div>
|
||||
</form>
|
||||
@@ -1,61 +0,0 @@
|
||||
<!--
|
||||
Repeat to local nginx
|
||||
-->
|
||||
<div class="form-group">
|
||||
<label>{{'input_titel' | translate}} <a href="https://datarhei.github.io/restreamer/docs/references-rtmp-rtsp-video-source.html", target="_blank"><span class="glyphicon glyphicon-question-sign" style="font-size: 16px;" aria-hidden="true"></span></a></label>
|
||||
<input type="text" class="form-control input" id="input_uri" placeholder="{{'input_example' | translate}}" ng-model="reStreamerData.addresses.srcAddress" ng-disabled="nginxRepeatStreamConnecting() || nginxRepeatStreamConnected()">
|
||||
</div>
|
||||
<div class="jumbotron progress-bar-info progress-bar-striped" ng-if="nginxRepeatStreamConnecting()">{{'process_init' | translate}}</div>
|
||||
<div class="jumbotron progress-bar-success progress-bar-striped" ng-if="nginxRepeatStreamConnected()">
|
||||
{{'process_success' | translate}}
|
||||
<div ng-if="reStreamerData.progresses.repeatToLocalNginx.currentFps > 0">
|
||||
<span class="ffmpeg-progress fps">{{reStreamerData.progresses.repeatToLocalNginx.currentFps}}fps </span>
|
||||
/
|
||||
<span class="ffmpeg-progress kbps">{{reStreamerData.progresses.repeatToLocalNginx.currentKbps}}Kb/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-show="nginxRepeatStreamInputInvalid">{{'process_input_invalid' | translate}}</div>
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-if="nginxRepeatStreamError()">{{'process_failed' | translate}} {{'process_failed_retry' | translate}} {{reStreamerData.progresses.repeatToLocalNginx.retryCount}}/{{reStreamerData.progresses.repeatToLocalNginx.retryMax}}</div>
|
||||
<div class="form group" >
|
||||
<p class="player-link" ng-if="nginxRepeatStreamConnected()">
|
||||
<a href="#" style="text-decoration:underline" ng-click="openPlayer()">
|
||||
{{'player_link_titel' | translate}}
|
||||
</a>
|
||||
</p>
|
||||
<div class="text-right">
|
||||
<button type="button" class="btn btn-success" ng-if="showStartButton('repeatToLocalNginx')" ng-click="startStream('repeatToLocalNginx')">{{'button_start' | translate}}</button>
|
||||
<button type="button" class="btn btn-danger" ng-if="showStopButton('repeatToLocalNginx')" ng-click="stopStream('repeatToLocalNginx')">{{'button_stop' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
<!--
|
||||
Repeat to optional Output
|
||||
-->
|
||||
<h4>Optional:</h4>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" ng-model="activateOptionalOutput"> {{'output_optional' | translate}} <a href="https://datarhei.github.io/restreamer/docs/references-external-rtmp-streaming-server.html", target="_blank"><span class="glyphicon glyphicon-question-sign" style="font-size: 14px;" aria-hidden="true"></span></a>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-group" ng-show="activateOptionalOutput === true">
|
||||
<input type="text" class="form-control input" id="output_optional_uri" placeholder="{{'output_optional_example' | translate}}" ng-model="reStreamerData.addresses.optionalOutputAddress" ng-disabled="optionalOutputConnected()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="jumbotron progress-bar-info progress-bar-striped" ng-if="optionalOutputConnecting()">{{'process_init' | translate}}</div>
|
||||
<div class="jumbotron progress-bar-success progress-bar-striped" ng-if="optionalOutputConnected()">
|
||||
{{'process_success' | translate}}
|
||||
<div ng-if="reStreamerData.progresses.repeatToOptionalOutput.currentFps > 0">
|
||||
<span class="ffmpeg-progress fps">{{reStreamerData.progresses.repeatToOptionalOutput.currentFps}}fps </span>
|
||||
/
|
||||
<span class="ffmpeg-progress kbps">{{reStreamerData.progresses.repeatToOptionalOutput.currentKbps}}Kb/s</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-show="optionalOutputInputInvalid">{{'process_input_invalid' | translate}}</div>
|
||||
<div class="jumbotron progress-bar-danger progress-bar-striped" ng-if="optionalOutputError()">{{'process_failed' | translate}} {{'process_failed_retry' | translate}} {{reStreamerData.progresses.repeatToOptionalOutput.retryCount}}/{{reStreamerData.progresses.repeatToOptionalOutput.retryMax}}</div>
|
||||
<div class="text-right">
|
||||
<div class="btn btn-danger" ng-click="stopStream('repeatToOptionalOutput')" ng-if="showStopButton('repeatToOptionalOutput')">{{'button_stop' | translate}}</div>
|
||||
<div ng-show="activateOptionalOutput === true">
|
||||
<div class="btn btn-success" ng-click="startStream('repeatToOptionalOutput')" ng-if="showStartButton('repeatToOptionalOutput')">{{'button_start' | translate}}</div>
|
||||
</div>
|
||||
</div>
|
||||
Reference in New Issue
Block a user