Compare commits
359 Commits
stream_dis
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08b1dd0ba0 | ||
|
|
5c2a3a1fa5 | ||
|
|
67cc21b1fa | ||
|
|
c1a9c715a4 | ||
|
|
ce2e4eb836 | ||
|
|
9e81f203cb | ||
|
|
4a88f47af5 | ||
|
|
3f44579508 | ||
|
|
18490b0496 | ||
|
|
f0d1db9044 | ||
|
|
eaacb94c54 | ||
|
|
352138dfef | ||
|
|
3f90be8598 | ||
|
|
05649aa2fd | ||
|
|
f54adc6b94 | ||
|
|
625b080752 | ||
|
|
1920eb583c | ||
|
|
8be8128d6e | ||
|
|
88826db4fd | ||
|
|
6154b9b734 | ||
|
|
3d02d3a79b | ||
|
|
58d0292ef9 | ||
|
|
1f04169aa5 | ||
|
|
c128f1d3f2 | ||
|
|
fdfa0d8f6f | ||
|
|
9d666e0879 | ||
|
|
6f5ecf878c | ||
|
|
486d64ff19 | ||
|
|
9277f04b4b | ||
|
|
f4708c23f0 | ||
|
|
a87ad7d614 | ||
|
|
8709d37738 | ||
|
|
9fad572ec0 | ||
|
|
1ab3e39ba0 | ||
|
|
ec7cc4bc47 | ||
|
|
f53be95e70 | ||
|
|
1e86878d75 | ||
|
|
e7ace32c3c | ||
|
|
60d3e8f617 | ||
|
|
ff2130138a | ||
|
|
8e208789f0 | ||
|
|
7007ce71ed | ||
|
|
06933e47d8 | ||
|
|
85a89b9b3a | ||
|
|
8ea3b71844 | ||
|
|
6292e62858 | ||
|
|
82bd4f2d76 | ||
|
|
44657181a0 | ||
|
|
8f3c60a1a7 | ||
|
|
dc384ed554 | ||
|
|
12344c958f | ||
|
|
c73cc357b9 | ||
|
|
a68a43ef48 | ||
|
|
b8c453bf48 | ||
|
|
3e4662b337 | ||
|
|
a0c41bba87 | ||
|
|
2226ce41c2 | ||
|
|
355f6a7967 | ||
|
|
fa094782c7 | ||
|
|
487899f934 | ||
|
|
318a1e32f4 | ||
|
|
82796c3801 | ||
|
|
cfb70e5325 | ||
|
|
de6fa8d64f | ||
|
|
4de6e111cc | ||
|
|
fa462d696d | ||
|
|
c2e95265ac | ||
|
|
906adcd5c5 | ||
|
|
9dfdc87983 | ||
|
|
939635ed94 | ||
|
|
3ef10bf093 | ||
|
|
78a6e68421 | ||
|
|
2c88c4dbde | ||
|
|
d931bc6e05 | ||
|
|
4e6725ae94 | ||
|
|
92ce3b5ba7 | ||
|
|
af690c689c | ||
|
|
4ac941b128 | ||
|
|
50c325fcce | ||
|
|
adf1b0b3b9 | ||
|
|
a1a79defd1 | ||
|
|
6b6efffe9f | ||
|
|
7bc922f1d5 | ||
|
|
77dfcbe749 | ||
|
|
813252d0c0 | ||
|
|
7d7c09c870 | ||
|
|
3270140555 | ||
|
|
febf34e8e4 | ||
|
|
ee2cc8eca7 | ||
|
|
88e16f187d | ||
|
|
ce1589ecde | ||
|
|
6d6028125c | ||
|
|
d98ff905b0 | ||
|
|
fda9f2adda | ||
|
|
80c01f93ad | ||
|
|
e92f87544e | ||
|
|
53e809685c | ||
|
|
ff79efca02 | ||
|
|
12ab148f47 | ||
|
|
d74438e300 | ||
|
|
42013ec2c1 | ||
|
|
4854c63fb1 | ||
|
|
c1f9b95a08 | ||
|
|
bcd3b7ba52 | ||
|
|
f4c9fbe61a | ||
|
|
dae04e7882 | ||
|
|
9752376b4a | ||
|
|
f57e1f6365 | ||
|
|
6f34336c32 | ||
|
|
729ad48cc4 | ||
|
|
5e2dff2a7e | ||
|
|
3c468887f7 | ||
|
|
6a24e68055 | ||
|
|
f809eacabf | ||
|
|
72312312ca | ||
|
|
62bc085ee4 | ||
|
|
287b50dab2 | ||
|
|
db1b5227fb | ||
|
|
c263f1f0d4 | ||
|
|
a85c5241f9 | ||
|
|
c316e36c1d | ||
|
|
245f69cdcb | ||
|
|
a68a39b9f9 | ||
|
|
e579b842f3 | ||
|
|
8565350b66 | ||
|
|
377acf12ff | ||
|
|
a7d2ac4ec7 | ||
|
|
964a7cd745 | ||
|
|
5dd710fc47 | ||
|
|
46c3ac5139 | ||
|
|
042029cb2e | ||
|
|
546024fc90 | ||
|
|
bffb228b1d | ||
|
|
5223e27079 | ||
|
|
92059f7579 | ||
|
|
b089edc02a | ||
|
|
74c71445b4 | ||
|
|
7888247f23 | ||
|
|
39dfd348db | ||
|
|
79610e2e4e | ||
|
|
0535ad8f51 | ||
|
|
763618cc4f | ||
|
|
bda1e18619 | ||
|
|
1a44e78eb7 | ||
|
|
d4edd0e399 | ||
|
|
94eb24c952 | ||
|
|
8de6830f3f | ||
|
|
28521b7a52 | ||
|
|
fec59ef28b | ||
|
|
9cc06d6ce6 | ||
|
|
93316b938b | ||
|
|
3d9d663e35 | ||
|
|
a87a02aaff | ||
|
|
4ff4c47ae2 | ||
|
|
5512154a22 | ||
|
|
e4676fded7 | ||
|
|
f333d7fe95 | ||
|
|
0da1d6ba49 | ||
|
|
30751629fd | ||
|
|
7afd632544 | ||
|
|
09b3630298 | ||
|
|
abae3696c9 | ||
|
|
f42e50dd95 | ||
|
|
41d166a67f | ||
|
|
87f4e443b5 | ||
|
|
99c90b4cab | ||
|
|
42f918baf2 | ||
|
|
fcd4231a3c | ||
|
|
cef6efbac0 | ||
|
|
718b00fe97 | ||
|
|
113fe20fc2 | ||
|
|
3d337541ce | ||
|
|
836f06b746 | ||
|
|
7a56fdeeaf | ||
|
|
3735ac2242 | ||
|
|
168b1fbaa3 | ||
|
|
ca966fbcba | ||
|
|
7538e083e3 | ||
|
|
2f78a621fe | ||
|
|
3961343446 | ||
|
|
3da606c8dd | ||
|
|
b39e04ec68 | ||
|
|
8e07fcef80 | ||
|
|
43e691b9ce | ||
|
|
e299dfd3f6 | ||
|
|
cfd71d7b9b | ||
|
|
085f6fe4d1 | ||
|
|
94f7715b2b | ||
|
|
b34906ce91 | ||
|
|
d7f64ccfef | ||
|
|
5dc24dca88 | ||
|
|
29ee83a4ce | ||
|
|
8b402acbbe | ||
|
|
ab0ba51802 | ||
|
|
3b9aabd2be | ||
|
|
eb89e6b942 | ||
|
|
f2b8071c70 | ||
|
|
39d9002076 | ||
|
|
a557e3afdb | ||
|
|
64d908ceec | ||
|
|
54fc55fc18 | ||
|
|
d18f99acdd | ||
|
|
638642d3de | ||
|
|
ec4edf2f47 | ||
|
|
d6b19e0e0f | ||
|
|
686e386f41 | ||
|
|
410a91d3c7 | ||
|
|
98b827b9f3 | ||
|
|
a844bf362d | ||
|
|
491124df1f | ||
|
|
14e217172b | ||
|
|
233393400d | ||
|
|
6638b42e72 | ||
|
|
7fcc5bb65d | ||
|
|
1b9cfa279f | ||
|
|
1d25b84ac5 | ||
|
|
e0982a1941 | ||
|
|
8a45e4c41c | ||
|
|
eac85115c2 | ||
|
|
268ca46669 | ||
|
|
d2b39753d2 | ||
|
|
c91fc9d2fe | ||
|
|
e45f9f0f98 | ||
|
|
3920d37c7a | ||
|
|
7456ed515a | ||
|
|
4099f463fa | ||
|
|
8e7ec934d3 | ||
|
|
731d4a93d8 | ||
|
|
4ec02d1b2d | ||
|
|
3a5a91c2c9 | ||
|
|
3610a2af5a | ||
|
|
40594b2e51 | ||
|
|
500faeb2a7 | ||
|
|
4f01a364d3 | ||
|
|
7ea182ce05 | ||
|
|
e238ae6119 | ||
|
|
cda68a89fb | ||
|
|
b969b1661d | ||
|
|
f9ae5d6527 | ||
|
|
71bfb9d1f1 | ||
|
|
f6e9328ff5 | ||
|
|
e795c2b33e | ||
|
|
f89aad775f | ||
|
|
7f18e29754 | ||
|
|
6552303505 | ||
|
|
38036dc0eb | ||
|
|
5e09f41707 | ||
|
|
8ed6605fc3 | ||
|
|
36c72ab6cc | ||
|
|
b4b8929a0d | ||
|
|
fb650243da | ||
|
|
0edf69ed7c | ||
|
|
44f7377669 | ||
|
|
1924499cdb | ||
|
|
167efb399e | ||
|
|
1ce00926d0 | ||
|
|
3e0d0739e5 | ||
|
|
cb9b59a029 | ||
|
|
1fffde2e41 | ||
|
|
157d06c8b9 | ||
|
|
7e331d5f82 | ||
|
|
c008fb1eef | ||
|
|
c367261053 | ||
|
|
f836a244bf | ||
|
|
af9603575a | ||
|
|
24b14a2d75 | ||
|
|
b5967f24e6 | ||
|
|
9d3fdb5c41 | ||
|
|
76ac60cc5b | ||
|
|
c5a91815fa | ||
|
|
df9ae03900 | ||
|
|
e587f2076b | ||
|
|
8a0521846b | ||
|
|
7a8a04c442 | ||
|
|
afe79a21a6 | ||
|
|
a1bfd104ae | ||
|
|
f17112a97f | ||
|
|
dc84aae8ce | ||
|
|
7f1d2c7df1 | ||
|
|
d348e702a2 | ||
|
|
0795d5fba8 | ||
|
|
4017c1d2a3 | ||
|
|
a59c766c4d | ||
|
|
2cb2881e3c | ||
|
|
3cfec164bd | ||
|
|
183717e98a | ||
|
|
42ca7d2ae2 | ||
|
|
d976461ab0 | ||
|
|
759886afda | ||
|
|
629d8feed5 | ||
|
|
59cc4c67e8 | ||
|
|
a91ced0e6d | ||
|
|
759de38c8c | ||
|
|
c9b8b6abb2 | ||
|
|
3dae99a474 | ||
|
|
7ffe697939 | ||
|
|
2ac8bb5e87 | ||
|
|
b2a6a5d3a9 | ||
|
|
31cc0ec139 | ||
|
|
781a7ce0ec | ||
|
|
9b86bca643 | ||
|
|
2c480dc67a | ||
|
|
bdc212679a | ||
|
|
2ab458ca53 | ||
|
|
f4ab7f32b9 | ||
|
|
dae3452a3f | ||
|
|
0ee4e42360 | ||
|
|
2f40d96412 | ||
|
|
67aad79bfe | ||
|
|
dec533b5b7 | ||
|
|
2bf30ca450 | ||
|
|
b3722ec5cb | ||
|
|
d7dd3b0969 | ||
|
|
b4ecf7309a | ||
|
|
d19c96ac3f | ||
|
|
b952e4680f | ||
|
|
94f5d9e1ee | ||
|
|
5eb60f2b19 | ||
|
|
4e04c1caec | ||
|
|
d4f8f25d65 | ||
|
|
9aaaf8e61b | ||
|
|
b5f0fe386e | ||
|
|
2ccec4873d | ||
|
|
8d5b1eca3b | ||
|
|
f116071c98 | ||
|
|
5bb0c1ac1a | ||
|
|
de54392681 | ||
|
|
8eca5d79db | ||
|
|
2eb41c83bd | ||
|
|
fd209165e5 | ||
|
|
7849256593 | ||
|
|
10ffb16592 | ||
|
|
f4c3f64b42 | ||
|
|
48792716a1 | ||
|
|
df0fe680a9 | ||
|
|
bc7697ad57 | ||
|
|
5ccf1d552f | ||
|
|
5f85f3aef9 | ||
|
|
2ded21e988 | ||
|
|
2b9a9a1207 | ||
|
|
9d7431a4bd | ||
|
|
db7243489f | ||
|
|
23a274455d | ||
|
|
b9c7bda69e | ||
|
|
1984b9d12c | ||
|
|
21f3829af5 | ||
|
|
82531a9226 | ||
|
|
a55924d28c | ||
|
|
9e5a9dd8aa | ||
|
|
e012fec51d | ||
|
|
27eb93e465 | ||
|
|
154e126716 | ||
|
|
482689d245 | ||
|
|
81737841b9 | ||
|
|
ad91c38d5a | ||
|
|
cfde8db58b | ||
|
|
92ec93dff0 | ||
|
|
0af3a66687 | ||
|
|
ce15947f11 |
@ -10,3 +10,4 @@ node_modules/
|
|||||||
.github
|
.github
|
||||||
.github_build
|
.github_build
|
||||||
.build
|
.build
|
||||||
|
NONPUBLIC/
|
||||||
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
src/locales/*/messages.js
|
||||||
129
.github/workflows/build_restreamer-ui.yaml
vendored
@ -1,61 +1,88 @@
|
|||||||
name: 'Build restreamer-ui'
|
name: 'Build main restreamer-ui'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
push:
|
|
||||||
branches-ignore:
|
|
||||||
- '**'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
docker:
|
build-frontend:
|
||||||
runs-on: [self-hosted]
|
runs-on: ubuntu-latest
|
||||||
steps:
|
outputs:
|
||||||
- name: Checkout
|
version: ${{ steps.latestversion.outputs.version }}
|
||||||
uses: actions/checkout@v2
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: cardinalby/export-env-action@v1
|
- name: Get latest version from package.json
|
||||||
with:
|
id: latestversion
|
||||||
envFile: '.github_build/Build.restreamer-ui.env'
|
run: |
|
||||||
export: 'true'
|
echo "version=$(cat ./package.json | jq '.version' | sed 's/\"//g')" >> "$GITHUB_OUTPUT"
|
||||||
expandWithJobEnv: 'true'
|
|
||||||
expand: 'true'
|
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up Node.js
|
||||||
uses: docker/setup-qemu-action@master
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
platforms: all
|
node-version: '21'
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Build React App
|
||||||
id: buildx
|
run: |
|
||||||
uses: docker/setup-buildx-action@master
|
yarn install
|
||||||
|
yarn build
|
||||||
|
env:
|
||||||
|
PUBLIC_URL: './'
|
||||||
|
|
||||||
- name: Cache Docker layers
|
- name: Upload React build as artifact
|
||||||
uses: actions/cache@v2
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
path: /tmp/.buildx-cache
|
name: restreamerui-main-build
|
||||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
path: build/
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-buildx-
|
|
||||||
|
|
||||||
- name: Login to DockerHub
|
build-docker:
|
||||||
if: github.event_name != 'pull_request'
|
needs: build-frontend
|
||||||
uses: docker/login-action@v1
|
runs-on: [self-hosted]
|
||||||
with:
|
steps:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
- name: Checkout
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Build Multi-Arch
|
- name: Download React build artifact
|
||||||
uses: docker/build-push-action@v2
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
builder: ${{ steps.buildx.outputs.name }}
|
name: restreamerui-main-build
|
||||||
context: .
|
path: build
|
||||||
file: ./Dockerfile
|
|
||||||
build-args: |
|
- name: Docker meta
|
||||||
PUBLIC_URL=/ui
|
id: meta
|
||||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
uses: docker/metadata-action@v5
|
||||||
push: true
|
with:
|
||||||
tags: |
|
images: |
|
||||||
datarhei/restreamer-ui:${{ env.RELEASE }}
|
datarhei/restreamer-ui
|
||||||
datarhei/restreamer-ui:latest
|
tags: |
|
||||||
cache-from: type=local,src=/tmp/.buildx-cache
|
type=raw,value=latest
|
||||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
type=raw,value=${{ needs.build-frontend.outputs.version }}
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@master
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@master
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and Push Docker Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.workflow
|
||||||
|
build-args: |
|
||||||
|
CADDY_IMAGE=caddy:2.7.6-alpine
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
|||||||
88
.github/workflows/build_restreamer-ui_dev.yaml
vendored
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
name: 'Build dev restreamer-ui'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
workflow_call:
|
||||||
|
schedule:
|
||||||
|
- cron: '37 4 * * *'
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- dev
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-frontend:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: dev
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '21'
|
||||||
|
|
||||||
|
- name: Build React App
|
||||||
|
run: |
|
||||||
|
yarn install
|
||||||
|
yarn build
|
||||||
|
env:
|
||||||
|
PUBLIC_URL: './'
|
||||||
|
|
||||||
|
- name: Upload React build as artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: restreamerui-dev-build
|
||||||
|
path: build/
|
||||||
|
|
||||||
|
build-docker:
|
||||||
|
needs: build-frontend
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download React build artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: restreamerui-dev-build
|
||||||
|
path: build
|
||||||
|
|
||||||
|
- name: Docker meta
|
||||||
|
id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
datarhei/restreamer-ui
|
||||||
|
tags: |
|
||||||
|
type=raw,value=dev
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@master
|
||||||
|
with:
|
||||||
|
platforms: all
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@master
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: github.event_name != 'pull_request'
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build and Push Docker Image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
builder: ${{ steps.buildx.outputs.name }}
|
||||||
|
context: .
|
||||||
|
file: ./Dockerfile.workflow
|
||||||
|
build-args: |
|
||||||
|
CADDY_IMAGE=caddy:2.7.6-alpine
|
||||||
|
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
@ -1,2 +0,0 @@
|
|||||||
# RESTREAMER UI
|
|
||||||
RELEASE=1.1.0
|
|
||||||
2
.gitignore
vendored
@ -1,6 +1,7 @@
|
|||||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
# dependencies
|
# dependencies
|
||||||
|
/NONPUBLIC
|
||||||
/node_modules
|
/node_modules
|
||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
@ -12,6 +13,7 @@
|
|||||||
/build
|
/build
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
|
NONPUBLIC
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.VSCodeCounter
|
.VSCodeCounter
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
13
.linguirc
@ -14,12 +14,19 @@
|
|||||||
"sourceLocale": "en",
|
"sourceLocale": "en",
|
||||||
"locales": [
|
"locales": [
|
||||||
"en",
|
"en",
|
||||||
|
"da",
|
||||||
"de",
|
"de",
|
||||||
|
"el",
|
||||||
|
"es",
|
||||||
"fr",
|
"fr",
|
||||||
"it",
|
"it",
|
||||||
|
"ko",
|
||||||
"pl",
|
"pl",
|
||||||
"pt",
|
"pt-br",
|
||||||
"es",
|
"ru",
|
||||||
"ru"
|
"sl",
|
||||||
|
"tr",
|
||||||
|
"uk",
|
||||||
|
"zh-hans"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
196
CHANGELOG.md
@ -1,19 +1,201 @@
|
|||||||
# Restreamer-UI
|
# Restreamer-UI
|
||||||
|
|
||||||
#### v1.1.0 > v1.2.0
|
## v1.13.0 > v1.14.0
|
||||||
|
|
||||||
|
- Add wettercom service
|
||||||
|
- Add option to select which channels will be displayed on the playersite ([#392](https://github.com/datarhei/restreamer/issues/392), [#800](https://github.com/datarhei/restreamer/issues/800))
|
||||||
|
- Mod updates public videojs >v8
|
||||||
|
- Fix erroneous filter setting
|
||||||
|
- Fix encoded address
|
||||||
|
- Fix double -filter parameter when encoder sets filter
|
||||||
|
- Fix Docker build ([#64](https://github.com/datarhei/restreamer-ui/issues/64))
|
||||||
|
|
||||||
|
## v1.12.0 > v1.13.0
|
||||||
|
|
||||||
|
- Add to allow stream hints in case probing fails
|
||||||
|
- Mod enables ff-loglevel and prepares the logging component
|
||||||
|
- Mod uses official Instagram-RTMP target
|
||||||
|
- Mod Remove unused imports
|
||||||
|
- Mod Update translations
|
||||||
|
- Mod updates dep.
|
||||||
|
- Fix player position
|
||||||
|
- Fix missing stream URL, summarize streams in probe log, don't lock type for first stream
|
||||||
|
|
||||||
|
## v1.11.0 > v1.12.0
|
||||||
|
|
||||||
|
- Add option to select different SRT stream in wizard
|
||||||
|
- Add option to select different RTMP stream in wizard
|
||||||
|
- Fix selecting other than first audio stream ([#710](https://github.com/datarhei/restreamer/issues/710))
|
||||||
|
- Fix reset of previous audio settings when editing profile ([#730](https://github.com/datarhei/restreamer/issues/730))
|
||||||
|
- Fix RTMP URL for receive mode
|
||||||
|
|
||||||
|
## v1.10.0 > v1.11.0
|
||||||
|
|
||||||
|
- Add allow to stream HEVC and AV1 to Youtube via RTMP
|
||||||
|
- Add librav1e AV1 encoder
|
||||||
|
- Add support for AV1 CUDA decoding ([PR 46](https://github.com/datarhei/restreamer-ui/pull/46))
|
||||||
|
- Add FFmpeg 6 support
|
||||||
|
- Add HEVC VideoToolbox encoder
|
||||||
|
- Fix anonymize error message ([#688](https://github.com/datarhei/restreamer/issues/688))
|
||||||
|
- Fix chromecast config ([#37](https://github.com/datarhei/restreamer-ui/issues/37))
|
||||||
|
|
||||||
|
## v1.9.0 > v1.10.0
|
||||||
|
|
||||||
|
- Add resource usage and ffmpeg command to process details
|
||||||
|
- Add audio loop source
|
||||||
|
- Add to allow to select from already publishing RTMP and SRT streams
|
||||||
|
- Fix wrongly displayed SRT URL ([#635](https://github.com/datarhei/restreamer/issues/635))
|
||||||
|
- Fix RTMPS address with custom ports ([#658](https://github.com/datarhei/restreamer/issues/658))
|
||||||
|
- Fix allow RTSPS protocol ([#677](https://github.com/datarhei/restreamer/issues/677))
|
||||||
|
|
||||||
|
## v1.8.0 > v1.9.0
|
||||||
|
|
||||||
|
- Add enlarged channel overview
|
||||||
|
- Add new publication services: Dailymotion, Livepush, kick.com, NimoTV, PicartoTV, Rumble
|
||||||
|
- Add frame interpolation (framerate) filter (thanks to orryverducci)
|
||||||
|
- Add -referer option for pulling HTTP streams ([PR 40](https://github.com/datarhei/restreamer-ui/pull/40), thanks to mdastgheib)
|
||||||
|
- Add a/v filter to the publication components ([#593](https://github.com/datarhei/restreamer-ui/issues/593))
|
||||||
|
- Add video or image loop as input ([#528](https://github.com/datarhei/restreamer/discussions/528))
|
||||||
|
- Add option for custom poster image in player ([#632](https://github.com/datarhei/restreamer/issues/632))
|
||||||
|
- Add option to allow to set limits for ingest and egress processes ([#636](https://github.com/datarhei/restreamer/issues/636))
|
||||||
|
- Mod extends twitch's server list
|
||||||
|
- Mod uses placeholders for ingress setups ([#560](https://github.com/datarhei/restreamer-ui/issues/560))
|
||||||
|
- Mod updates npm
|
||||||
|
- Fix Owncast typo
|
||||||
|
- Fix Restream grid
|
||||||
|
- Fix the advanced settings in the MPEG-TS publication service ([#597](https://github.com/datarhei/restreamer/issues/597), thanks to orryverducci)
|
||||||
|
- Fix ALSA demuxer option names
|
||||||
|
- Fix index out-of-range warning, list ALSA devices for Raspicam video source
|
||||||
|
- Fix MUI warning
|
||||||
|
- Fix videojs skin
|
||||||
|
|
||||||
|
## v1.7.0 > v1.8.0
|
||||||
|
|
||||||
|
- Add stream key field and protocol detection to RTMP publication service
|
||||||
|
- Add Chinese (simplified) translation (thanks to Huyg0180110559)
|
||||||
|
- Add Ukrainian translation (thanks to Yurii Denys)
|
||||||
|
- Fix empty force_key_frames value
|
||||||
|
- Fix Icecast publication service
|
||||||
|
- Fix imprint, terms and credit without share ([#525](https://github.com/datarhei/restreamer/issues/529))
|
||||||
|
- Fix proxy error on the playersite ([#525](https://github.com/datarhei/restreamer/issues/525))
|
||||||
|
- Fix saving RTMP advanced options ([#518](https://github.com/datarhei/restreamer/issues/518))
|
||||||
|
- Fix help buttons for other languages than English and German ([#24](https://github.com/datarhei/restreamer-ui/issues/24))
|
||||||
|
- Fix internal player skin (volume bar)
|
||||||
|
- Fix security hints (npm dep.)
|
||||||
|
|
||||||
|
## v1.6.0 > v1.7.0
|
||||||
|
|
||||||
|
- Add analyzeduration, probesize and max_probe_packets input options
|
||||||
|
- Add avoid_negative_ts input option
|
||||||
|
- Add http_proxy input option ([#513](https://github.com/datarhei/restreamer/issues/513))
|
||||||
|
- Add copyts, start_at_zero and use_wallclock_as_timestamps input options
|
||||||
|
- Add heuristic to find core address if UI is proxied
|
||||||
|
- Add Turkish translation (thanks to Ramazan Sancar) ([#22](https://github.com/datarhei/restreamer-ui/issues/22))
|
||||||
|
- Add Danish translation (Thanks to Filip Stadler and Info)
|
||||||
|
- Add Slovenian translation (thanks to Grega)
|
||||||
|
- Add Greek translation
|
||||||
|
- Mod allows general input settings for pull and push streams
|
||||||
|
- Mod updates npm dependencies
|
||||||
|
- Fix Creative Commons icons
|
||||||
|
- Fix positioning of the deinterlacing filter ([#465](https://github.com/datarhei/restreamer/issues/465))
|
||||||
|
|
||||||
|
## v1.5.1 > v1.6.0
|
||||||
|
|
||||||
|
- Add Bob Weaver Deinterlacing Filter ([#465](https://github.com/datarhei/restreamer/issues/465))
|
||||||
|
- Add tests for wizard, network source, and coders
|
||||||
|
- Add Korean translation (thanks to Jihaeng)
|
||||||
|
- Mod splitting wizard in components
|
||||||
|
- Fix wrong call to encoder defaults ([#467](https://github.com/datarhei/restreamer/issues/467))
|
||||||
|
|
||||||
|
## v1.5.0 > v1.5.1
|
||||||
|
|
||||||
|
- Fix FFmpeg version check for RTSP sources ([#455](https://github.com/datarhei/restreamer/issues/455))
|
||||||
|
- Fix requires Core >= v16.11.0 and FFmpeg >= 5.1.0
|
||||||
|
|
||||||
|
## v1.4.0 > v1.5.0
|
||||||
|
|
||||||
|
- Add changelog viewer
|
||||||
|
- Add skills props to encoder and decoder components
|
||||||
|
- Add fps_mode to x264, x265, vp9 encoder
|
||||||
|
- Add scale filter to non-hwaccel encoders
|
||||||
|
- Add PeerTube and Media Network to publication services (plattforms, software)
|
||||||
|
- Add reset button to hide a player logo ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||||
|
- Mod expands V4L2_M2M options (an unstable RPI 64bit encoder)
|
||||||
|
- Mod indicates a faulty cache configuration
|
||||||
|
- Mod switches to the improved SRT syntax (thx to SA Consulting)
|
||||||
|
- Mod improves display of progress data
|
||||||
|
- Mod removes deprecated param ocl - now ochl (ff5)
|
||||||
|
- Mod simplifies the setup of Restreamer-to-Restreamer connections
|
||||||
|
- Mod adds Istafeed.me as StreamKey service to Instagram's publishing service
|
||||||
|
- Mod renames "Low delay" to "Low latency (buffer)" and set false as default (requires more feedback)
|
||||||
|
- Del removes support for clappr player
|
||||||
|
- Fix npm dependencies (security fixes)
|
||||||
|
- Fix videojs-overlay logo size ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||||
|
- Fix use of TLS for input from local RTMP server
|
||||||
|
- Fix Icecast publication service settings (datarhei/restreamer#429)
|
||||||
|
- Fix removes SRT bitstream on tee (OBS > RTMP > SRT is faulty)
|
||||||
|
|
||||||
|
Dependency:
|
||||||
|
|
||||||
|
- datarhei Core v16.11.0+
|
||||||
|
|
||||||
|
## v1.3.0 > v1.4.0
|
||||||
|
|
||||||
|
- Add email field for Let's Encrypt certification
|
||||||
|
|
||||||
|
Dependency:
|
||||||
|
|
||||||
|
- datarhei Core v16.10.1+
|
||||||
|
|
||||||
|
## v1.2.0 > v1.3.0
|
||||||
|
|
||||||
|
- Add dlive & Trovo publication services
|
||||||
|
- Add low_delay option to processing (default: true)
|
||||||
|
- Mod uses the ingest stream for publication (datarhei/restreamer#411)
|
||||||
|
- Mod optimized DVR on DiskFS
|
||||||
|
- Mod updates packages
|
||||||
|
- Fix SRT bitstream on tee
|
||||||
|
- Fix typo
|
||||||
|
- Fix viewer count (datarhei/restreamer#394)
|
||||||
|
- Fix user registration if username and/or password are set via environment (datarhei/restreamer-ui#13)
|
||||||
|
- Fix Dockerfile, Reduce size, serve production build (datarhei/restreamer-ui#12)
|
||||||
|
|
||||||
|
Dependency:
|
||||||
|
|
||||||
|
- datarhei Core v16.10.0+
|
||||||
|
|
||||||
|
## v1.1.0 > v1.2.0
|
||||||
|
|
||||||
|
- Add allow writing HLS to disk
|
||||||
|
- Add audio pan filter
|
||||||
|
- Add video rotation filter ([#347](https://github.com/datarhei/restreamer/discussions/347))
|
||||||
|
- Add video h/v flip filter
|
||||||
|
- Add audio volume filter ([#313](https://github.com/datarhei/restreamer/issues/313))
|
||||||
|
- Add audio loudness normalization filter
|
||||||
|
- Add audio resample filter, that was before part of the encoders
|
||||||
|
- Add HLS Master playlist (requires FFmpeg hlsbitrate.patch) (thx Dwaynarang, Electra Player compatibility)
|
||||||
|
- Add LinkedIn & Azure Media Services to publication services (thx kalashnikov)
|
||||||
|
- Add AirPlay support with silvermine videojs plugin
|
||||||
|
- Add Chromecast support (thx badincite, [#10](https://github.com/datarhei/restreamer-ui/pull/10))
|
||||||
|
- Add stream distribution across multiple internal servers
|
||||||
- Add SRT settings
|
- Add SRT settings
|
||||||
- Add HLS version selection (Dwaynarang, Electra Player compatibility)
|
- Add HLS version selection (thx Dwaynarang, Electra Player compatibility)
|
||||||
- Add Owncast to publication services ([#369](https://github.com/datarhei/restreamer/issues/369))
|
- Add Owncast to publication services ([#369](https://github.com/datarhei/restreamer/issues/369))
|
||||||
- Add Telegram to publication services (thx Martin Held)
|
- Add Telegram to publication services (thx Martin Held)
|
||||||
- Add Polish translations (thx Robert Rykała)
|
- Add Polish translations (thx Robert Rykała)
|
||||||
- Mod Allow decoders and encoders to set global options
|
- Mod extends the datarhei Core publication service with srt streaming
|
||||||
|
- Mod allow decoders and encoders to set global options
|
||||||
|
- Mod allow trailing slash on Core address
|
||||||
|
- Fix player problem with different stream formats (9:16)
|
||||||
- Fix process report naming
|
- Fix process report naming
|
||||||
- Fix publication service icon styles
|
- Fix publication service icon styles
|
||||||
- Fix VAAPI encoder
|
- Fix VAAPI encoder
|
||||||
- Allow trailing slash on Core address
|
|
||||||
|
|
||||||
#### v1.0.0 > v1.1.0
|
Dependency:
|
||||||
|
|
||||||
|
- datarhei Core v16.9.0+
|
||||||
|
|
||||||
|
## v1.0.0 > v1.1.0
|
||||||
|
|
||||||
- Add compatibility list for encoders
|
- Add compatibility list for encoders
|
||||||
- Add "HLS cleanup" as an optional function ([Philipp Trenz](https://github.com/philipptrenz))
|
- Add "HLS cleanup" as an optional function ([Philipp Trenz](https://github.com/philipptrenz))
|
||||||
@ -22,8 +204,9 @@
|
|||||||
- Add missed VAAPI encoder
|
- Add missed VAAPI encoder
|
||||||
- Add missed V4L2_M2M encoder
|
- Add missed V4L2_M2M encoder
|
||||||
- Add missed Raspberry Pi 64bit Docker image
|
- Add missed Raspberry Pi 64bit Docker image
|
||||||
- Mod updates VideoJS
|
|
||||||
- Add option to disable playersites share-button (thx Anders Mellgren)
|
- Add option to disable playersites share-button (thx Anders Mellgren)
|
||||||
|
- Add security pr
|
||||||
|
- Mod updates VideoJS
|
||||||
- Fix hides unset content license on playersite (thx Anders Mellgren)
|
- Fix hides unset content license on playersite (thx Anders Mellgren)
|
||||||
- Fix updates V4L2 device-list on select
|
- Fix updates V4L2 device-list on select
|
||||||
- Fix snapshot interval ([#341](https://github.com/datarhei/restreamer/issues/340))
|
- Fix snapshot interval ([#341](https://github.com/datarhei/restreamer/issues/340))
|
||||||
@ -34,7 +217,6 @@
|
|||||||
- Fix datarhei Core publication service
|
- Fix datarhei Core publication service
|
||||||
- Fix dependabot alerts
|
- Fix dependabot alerts
|
||||||
- Fix code scanning alerts
|
- Fix code scanning alerts
|
||||||
- Merge security pr
|
|
||||||
|
|
||||||
Preparation for FFmpeg v5.0 (migration will not work)
|
Preparation for FFmpeg v5.0 (migration will not work)
|
||||||
|
|
||||||
|
|||||||
6
Caddyfile
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
:3000
|
||||||
|
|
||||||
|
encode zstd gzip
|
||||||
|
file_server {
|
||||||
|
root ./build
|
||||||
|
}
|
||||||
26
Dockerfile
@ -1,23 +1,25 @@
|
|||||||
FROM node:17.9.0-alpine3.15
|
ARG NODE_IMAGE=node:21-alpine3.20
|
||||||
|
ARG CADDY_IMAGE=caddy:2.8.4-alpine
|
||||||
|
|
||||||
ARG NODE_SPACE_SIZE=10240
|
FROM $NODE_IMAGE AS builder
|
||||||
ENV NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=$NODE_SPACE_SIZE"
|
|
||||||
|
|
||||||
ARG PUBLIC_URL "/"
|
ENV PUBLIC_URL="./"
|
||||||
|
|
||||||
COPY . /ui
|
COPY . /ui
|
||||||
|
|
||||||
WORKDIR /ui
|
WORKDIR /ui
|
||||||
|
|
||||||
RUN cd /ui && \
|
RUN cd /ui && \
|
||||||
npm config set fetch-retries 10 && \
|
yarn install && \
|
||||||
npm config set fetch-retry-mintimeout 100000 && \
|
yarn build
|
||||||
npm config set fetch-retry-maxtimeout 600000 && \
|
|
||||||
npm config set cache-min 3600 && \
|
FROM $CADDY_IMAGE
|
||||||
npm config ls -l && \
|
|
||||||
npm install && \
|
COPY --from=builder /ui/build /ui/build
|
||||||
npm run build
|
COPY --from=builder /ui/Caddyfile /ui/Caddyfile
|
||||||
|
|
||||||
|
WORKDIR /ui
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
CMD [ "npm", "run", "start" ]
|
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]
|
||||||
|
|||||||
13
Dockerfile.workflow
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
ARG CADDY_IMAGE=caddy:2.7.5-alpine
|
||||||
|
FROM $CADDY_IMAGE
|
||||||
|
|
||||||
|
COPY build /ui/build
|
||||||
|
COPY Caddyfile /ui/Caddyfile
|
||||||
|
|
||||||
|
ENV PUBLIC_URL="./"
|
||||||
|
|
||||||
|
WORKDIR /ui
|
||||||
|
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]
|
||||||
@ -1,6 +1,6 @@
|
|||||||
# Restreamer-UI
|
# Restreamer-UI
|
||||||
|
|
||||||
The user interface of the Restreamer for the connection to the Core application.
|
The user interface of the Restreamer for the connection to the [datarhei Core](https://github.com/datarhei/core)application.
|
||||||
|
|
||||||
- React
|
- React
|
||||||
- Material-UI (MUI)
|
- Material-UI (MUI)
|
||||||
@ -17,7 +17,7 @@ $ npm run start
|
|||||||
```
|
```
|
||||||
|
|
||||||
Connect the UI with a [datarhei Core](https://github.com/datarhei/core):
|
Connect the UI with a [datarhei Core](https://github.com/datarhei/core):
|
||||||
http://localhost:3000?address=http://core-ip:core-port/
|
http://localhost:3000?address=http://core-ip:core-port
|
||||||
|
|
||||||
### To add/fix translations:
|
### To add/fix translations:
|
||||||
Locales are located in `src/locals`
|
Locales are located in `src/locals`
|
||||||
|
|||||||
105
package.json
@ -1,57 +1,61 @@
|
|||||||
{
|
{
|
||||||
"name": "restreamer-ui",
|
"name": "restreamer-ui",
|
||||||
"version": "1.2.0",
|
"version": "1.14.0",
|
||||||
"bundle": "restreamer-v2.x.x",
|
"bundle": "restreamer-v2.12.0",
|
||||||
"private": false,
|
"private": false,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@auth0/auth0-spa-js": "^1.22.0",
|
"@auth0/auth0-spa-js": "^2.1.3",
|
||||||
"@clappr/core": "^0.4.21",
|
"@babel/plugin-syntax-flow": "^7.24.7",
|
||||||
"@clappr/hlsjs-playback": "^0.6.0",
|
"@babel/plugin-transform-react-jsx": "^7.25.2",
|
||||||
"@clappr/plugins": "^0.4.16",
|
"@emotion/react": "^11.13.3",
|
||||||
"@clappr/stats-plugin": "^0.2.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@emotion/react": "^11.9.0",
|
"@fontsource/dosis": "^5.0.21",
|
||||||
"@emotion/styled": "^11.8.1",
|
"@fontsource/roboto": "^5.0.14",
|
||||||
"@fontsource/dosis": "^4.5.8",
|
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||||
"@fontsource/roboto": "^4.5.7",
|
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
"@lingui/core": "^4.11.4",
|
||||||
"@fortawesome/react-fontawesome": "^0.1.18",
|
"@lingui/macro": "^4.11.4",
|
||||||
"@lingui/core": "^3.13.3",
|
"@lingui/react": "^4.11.4",
|
||||||
"@lingui/macro": "^3.13.3",
|
"@mui/icons-material": "^6.0.1",
|
||||||
"@lingui/react": "^3.13.3",
|
"@mui/lab": "^6.0.0-beta.8",
|
||||||
"@mui/icons-material": "^5.8.2",
|
"@mui/material": "^6.0.1",
|
||||||
"@mui/lab": "^5.0.0-alpha.84",
|
"@mui/styles": "^6.0.1",
|
||||||
"@mui/material": "5.1.1",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@mui/styles": "^5.1.1",
|
"@testing-library/jest-dom": "^6.5.0",
|
||||||
"@testing-library/dom": "^8.13.0",
|
"@testing-library/react": "^16.0.1",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@testing-library/react": "^12.1.5",
|
"@types/react": "^18.3.5",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"eslint": "^7.32.0",
|
"eslint": "^9.9.1",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.8",
|
||||||
"hls.js": "^0.14.17",
|
"jwt-decode": "^4.0.0",
|
||||||
"jwt-decode": "^3.1.2",
|
"make-plural": "^7.4.0",
|
||||||
"make-plural": "^7.1.0",
|
"react": "^18.3.1",
|
||||||
"react": "^17.0.2",
|
"react-colorful": "^5.6.1",
|
||||||
"react-colorful": "^5.5.1",
|
"react-device-detect": "^2.2.3",
|
||||||
"react-device-detect": "^2.2.2",
|
"react-dom": "^18.3.1",
|
||||||
"react-dom": "^17.0.2",
|
"react-markdown": "^9.0.1",
|
||||||
"react-router-dom": "^6.3.0",
|
"react-router-dom": "^6.26.1",
|
||||||
"react-scripts": "^4.0.3",
|
"react-scripts": "^5.0.1",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.6.3",
|
||||||
"typescript": "^3.9.7",
|
"serve": "^14.2.3",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
"url-parse": "^1.5.10",
|
"url-parse": "^1.5.10",
|
||||||
"uuid": "^8.3.2",
|
"util": "^0.12.5",
|
||||||
"video.js": "^7.19.2",
|
"uuid": "^10.0.0",
|
||||||
"videojs-overlay": "^2.1.5"
|
"video.js": "^8.17.3",
|
||||||
|
"videojs-overlay": "^3.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts --optimize-for-size build",
|
"build": "react-scripts --optimize-for-size build",
|
||||||
|
"start-build": "serve -s build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
"test-ci": "react-scripts test --watchAll=false --testTimeout 50000",
|
||||||
|
"test-coverage": "react-scripts test --watchAll=false --testTimeout 50000 --coverage",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"i18n-extract": "lingui extract",
|
"i18n-extract": "lingui extract",
|
||||||
"i18n-extract:clean": "lingui extract --clean",
|
"i18n-extract:clean": "lingui extract --clean",
|
||||||
@ -73,7 +77,7 @@
|
|||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11, maintained node versions"
|
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11"
|
||||||
],
|
],
|
||||||
"development": [
|
"development": [
|
||||||
"last 1 chrome version",
|
"last 1 chrome version",
|
||||||
@ -82,14 +86,13 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.2",
|
"@babel/core": "^7.25.2",
|
||||||
"@lingui/cli": "^3.13.3",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"babel-core": "^7.0.0-bridge.0",
|
"@lingui/cli": "^4.11.4",
|
||||||
"prettier": "^2.6.2",
|
"babel-core": "^6.26.3",
|
||||||
|
"eslint-config-react-app": "^7.0.1",
|
||||||
|
"prettier": "^3.3.3",
|
||||||
"react-error-overlay": "^6.0.11"
|
"react-error-overlay": "^6.0.11"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {}
|
||||||
"url-parse@1.5.3": "patch:url-parse@npm:1.5.3#.yarn/patches/url-parse-npm-1.5.3-225ab9cae7.patch",
|
|
||||||
"react-error-overlay": "6.0.9"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
85
public/_player/clappr/dist/clappr.min.js
vendored
1
public/_player/clappr/dist/clappr.min.js.map
vendored
@ -1,4 +0,0 @@
|
|||||||
dist/clappr.min.js.map
|
|
||||||
dist/clappr.min.js
|
|
||||||
dist/clappr-stats.min.js
|
|
||||||
dist/clappr-nerd-stats.min.js
|
|
||||||
@ -1,112 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<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="{{description}}">
|
|
||||||
<meta name="author" content="datarhei restreamer">
|
|
||||||
<title>{{name}}</title>
|
|
||||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
|
||||||
<link rel="alternate" type="application/json+oembed" href="channels/{{channelid}}/oembed.json" title="{{name}}">
|
|
||||||
<link rel="alternate" type="text/xml+oembed" href="channels/{{channelid}}/oembed.xml" title="{{name}}">
|
|
||||||
<script src="channels/{{channelid}}/config.js"></script>
|
|
||||||
<script src="player/clappr/dist/clappr.min.js"></script>
|
|
||||||
<script src="player/clappr/dist/clappr-stats.min.js"></script>
|
|
||||||
<script src="player/clappr/dist/clappr-nerd-stats.min.js"></script>
|
|
||||||
<style>
|
|
||||||
.player-poster[data-poster] .poster-background[data-poster] {
|
|
||||||
height: initial !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="player" style="position:absolute;top:0;right:0;bottom:0;left:0"></div>
|
|
||||||
<script>
|
|
||||||
function getQueryParam(key, defaultValue) {
|
|
||||||
var query = window.location.search.substring(1);
|
|
||||||
var vars = query.split("&");
|
|
||||||
for(var i = 0; i < vars.length; i++) {
|
|
||||||
var pair = vars[i].split("=");
|
|
||||||
if(pair[0] == key) {
|
|
||||||
return pair[1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertBoolParam(key, defaultValue) {
|
|
||||||
var val = getQueryParam(key, defaultValue);
|
|
||||||
return val === true || val === "true" || val === "1" || val === "yes" || val === "on";
|
|
||||||
}
|
|
||||||
|
|
||||||
function convertColorParam(parameter, defaultColor) {
|
|
||||||
var re = new RegExp("^#([0-9a-f]{3}|[0-9a-f]{6})$");
|
|
||||||
var c = getQueryParam(parameter, defaultColor);
|
|
||||||
// decode color as # has to be represented by %23
|
|
||||||
var c = decodeURIComponent(c);
|
|
||||||
// if color was given without leading #, prepend it
|
|
||||||
if (!String(c).startsWith("#")) c = "#" + c;
|
|
||||||
|
|
||||||
if (re.test(c)) {
|
|
||||||
return c;
|
|
||||||
} else {
|
|
||||||
return defaultColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var autoplay = convertBoolParam("autoplay", playerConfig.autoplay);
|
|
||||||
var mute = convertBoolParam("mute", playerConfig.mute);
|
|
||||||
var statistics = convertBoolParam("stats", playerConfig.statistics);
|
|
||||||
var color = convertColorParam("color", playerConfig.color.buttons);
|
|
||||||
|
|
||||||
var plugins = [];
|
|
||||||
|
|
||||||
if(statistics == true) {
|
|
||||||
plugins.push(ClapprNerdStats);
|
|
||||||
plugins.push(ClapprStats);
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
source: playerConfig.source,
|
|
||||||
parentId: '#player',
|
|
||||||
baseUrl: 'clappr/',
|
|
||||||
plugins: plugins,
|
|
||||||
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
|
||||||
mediacontrol: {
|
|
||||||
seekbar: playerConfig.color.seekbar,
|
|
||||||
buttons: color
|
|
||||||
},
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
disableCanAutoPlay: true,
|
|
||||||
autoPlay: autoplay,
|
|
||||||
mute: mute,
|
|
||||||
clapprStats: {
|
|
||||||
runEach: 1000,
|
|
||||||
onReport: (metrics) => {},
|
|
||||||
},
|
|
||||||
clapprNerdStats: {
|
|
||||||
shortcut: ['command+shift+s', 'ctrl+shift+s'],
|
|
||||||
iconPosition: 'top-right'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(playerConfig.logo.image.length != 0) {
|
|
||||||
config.watermark = playerConfig.logo.image;
|
|
||||||
config.position = playerConfig.logo.position;
|
|
||||||
|
|
||||||
if(playerConfig.logo.link.length != 0) {
|
|
||||||
config.watermarkLink = playerConfig.logo.link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = new window.Clappr.Player(config);
|
|
||||||
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
|
|
||||||
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster () {
|
|
||||||
posterPlugin.options.poster = playerConfig.poster + '?t=' + String(new Date().getTime());
|
|
||||||
posterPlugin.render();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
15
public/_player/videojs/dist/ic_airplay_white_24px.svg
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<svg fill="#FFFFFF" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<defs>
|
||||||
|
<path d="M0 0h24v24H0V0z" id="a"/>
|
||||||
|
</defs>
|
||||||
|
<defs>
|
||||||
|
<path d="M0 0h24v24H0V0z" id="c"/>
|
||||||
|
</defs>
|
||||||
|
<clipPath id="b">
|
||||||
|
<use overflow="visible" xlink:href="#a"/>
|
||||||
|
</clipPath>
|
||||||
|
<clipPath clip-path="url(#b)" id="d">
|
||||||
|
<use overflow="visible" xlink:href="#c"/>
|
||||||
|
</clipPath>
|
||||||
|
<path clip-path="url(#d)" d="M6 22h12l-6-6zM21 3H3c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h4v-2H3V5h18v12h-4v2h4c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 623 B |
BIN
public/_player/videojs/dist/ic_cast_connected_white_24dp.png
vendored
Normal file
|
After Width: | Height: | Size: 981 B |
BIN
public/_player/videojs/dist/ic_cast_white_24dp.png
vendored
Normal file
|
After Width: | Height: | Size: 824 B |
37
public/_player/videojs/dist/video-js-skin.css
vendored
@ -95,7 +95,7 @@
|
|||||||
/* volume-panel */
|
/* volume-panel */
|
||||||
|
|
||||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
padding-top: 1em;
|
padding-top: .9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-control .vjs-volume-panel {
|
.vjs-public .vjs-control .vjs-volume-panel {
|
||||||
@ -110,8 +110,8 @@
|
|||||||
|
|
||||||
/* disable caps */
|
/* disable caps */
|
||||||
|
|
||||||
.vjs-internal .vjs-subs-caps-button {
|
.vjs-public .vjs-subs-caps-button {
|
||||||
display: none;
|
display: none!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* spacer */
|
/* spacer */
|
||||||
@ -123,33 +123,37 @@
|
|||||||
|
|
||||||
/* overlay */
|
/* overlay */
|
||||||
|
|
||||||
.vjs-public .vjs-overlay > a > img {
|
.vjs-public .vjs-overlay-no-background {
|
||||||
width: 100%;
|
max-width: 25%!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-no-background {
|
.vjs-public .vjs-overlay-no-background > img, .vjs-public .vjs-overlay-no-background > a > img {
|
||||||
max-width: 28%!important;
|
max-width: 100%!important;
|
||||||
max-height: 28%!important;
|
height: auto!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-left {
|
.vjs-public .vjs-overlay-top-left {
|
||||||
top: 20px!important;
|
top: 15px!important;
|
||||||
left: 30px!important;
|
left: 20px!important;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-right {
|
.vjs-public .vjs-overlay-top-right {
|
||||||
top: 20px!important;
|
top: 15px!important;
|
||||||
right: 30px!important;
|
right: 20px!important;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-left {
|
.vjs-public .vjs-overlay-bottom-left {
|
||||||
bottom: 20px!important;
|
bottom: 15px!important;
|
||||||
left: 30px!important;
|
left: 20px!important;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-right {
|
.vjs-public .vjs-overlay-bottom-right {
|
||||||
bottom: 20px!important;
|
bottom: 15px!important;
|
||||||
right: 30px!important;
|
right: 20px!important;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* context menu */
|
/* context menu */
|
||||||
@ -166,4 +170,3 @@
|
|||||||
.vjs-public .vjs-lock-open {
|
.vjs-public .vjs-lock-open {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public.vjs-big-play-button:focus,.vjs-public:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:0 0;background-image:linear-gradient(0deg,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay>a>img{width:100%}.vjs-public .vjs-overlay-no-background{max-width:28%!important;max-height:28%!important}.vjs-public .vjs-overlay-top-left{top:20px!important;left:30px!important}.vjs-public .vjs-overlay-top-right{top:20px!important;right:30px!important}.vjs-public .vjs-overlay-bottom-left{bottom:20px!important;left:30px!important}.vjs-public .vjs-overlay-bottom-right{bottom:20px!important;right:30px!important}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}
|
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:none;line-height:180px;font-size:180px;border:0;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public:hover .vjs-big-play-button,.vjs-public.vjs-big-play-button:focus{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:none;background-image:linear-gradient(0,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:.9em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-public .vjs-subs-caps-button{display:none!important}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay-no-background{max-width:25%!important}.vjs-public .vjs-overlay-no-background>img,.vjs-public .vjs-overlay-no-background>a>img{max-width:100%!important;height:auto!important}.vjs-public .vjs-overlay-top-left{top:15px!important;left:20px!important;text-align:right}.vjs-public .vjs-overlay-top-right{top:15px!important;right:20px!important;text-align:right}.vjs-public .vjs-overlay-bottom-left{bottom:15px!important;left:20px!important;text-align:left}.vjs-public .vjs-overlay-bottom-right{bottom:15px!important;right:20px!important;text-align:left}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}
|
||||||
778
public/_player/videojs/dist/video-js.css
vendored
2
public/_player/videojs/dist/video-js.min.css
vendored
46801
public/_player/videojs/dist/video.js
vendored
56
public/_player/videojs/dist/video.min.js
vendored
1
public/_player/videojs/dist/videojs-airplay.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-airplay-button-label{flex-grow:1;margin-left:4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}
|
||||||
308
public/_player/videojs/dist/videojs-airplay.js
vendored
Normal file
@ -0,0 +1,308 @@
|
|||||||
|
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var hasAirPlayAPISupport = require('../lib/hasAirPlayAPISupport');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the AirPlayButton Component with Video.js. Calls
|
||||||
|
* {@link http://docs.videojs.com/Component.html#.registerComponent}, which will add a
|
||||||
|
* component called `airPlayButton` to the list of globally registered Video.js
|
||||||
|
* components. The `airPlayButton` is added to the player's control bar UI automatically
|
||||||
|
* once {@link module:enableAirPlay} has been called. If you would like to specify the
|
||||||
|
* order of the buttons that appear in the control bar, including this button, you can do
|
||||||
|
* so in the options that you pass to the `videojs` function when creating a player:
|
||||||
|
*
|
||||||
|
* ```
|
||||||
|
* videojs('playerID', {
|
||||||
|
* controlBar: {
|
||||||
|
* children: [
|
||||||
|
* 'playToggle',
|
||||||
|
* 'progressControl',
|
||||||
|
* 'volumePanel',
|
||||||
|
* 'fullscreenToggle',
|
||||||
|
* 'airPlayButton',
|
||||||
|
* ],
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param videojs {object} A reference to {@link http://docs.videojs.com/module-videojs.html|Video.js}
|
||||||
|
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin
|
||||||
|
*/
|
||||||
|
module.exports = function (videojs) {
|
||||||
|
/**
|
||||||
|
* The AirPlayButton module contains both the AirPlayButton class definition and the
|
||||||
|
* function used to register the button as a Video.js Component.
|
||||||
|
*
|
||||||
|
* @module AirPlayButton
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ButtonComponent = videojs.getComponent('Button');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Video.js Button class is the base class for UI button components.
|
||||||
|
*
|
||||||
|
* @external Button
|
||||||
|
* @see {@link http://docs.videojs.com/Button.html|Button}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/** @lends AirPlayButton.prototype */
|
||||||
|
class AirPlayButton extends ButtonComponent {
|
||||||
|
/**
|
||||||
|
* This class is a button component designed to be displayed in the
|
||||||
|
* player UI's control bar. It displays an Apple AirPlay selection
|
||||||
|
* list when clicked.
|
||||||
|
*
|
||||||
|
* @constructs
|
||||||
|
* @extends external:Button
|
||||||
|
*/
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
if (!hasAirPlayAPISupport()) {
|
||||||
|
this.hide();
|
||||||
|
}
|
||||||
|
this._reactToAirPlayAvailableEvents();
|
||||||
|
if (options.addAirPlayLabelToButton) {
|
||||||
|
this.el().classList.add('vjs-airplay-button-lg');
|
||||||
|
this._labelEl = document.createElement('span');
|
||||||
|
this._labelEl.classList.add('vjs-airplay-button-label');
|
||||||
|
this._labelEl.textContent = this.localize('AirPlay');
|
||||||
|
this.el().appendChild(this._labelEl);
|
||||||
|
} else {
|
||||||
|
this.controlText('Start AirPlay');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides Button#buildCSSClass to return the classes used on the button element.
|
||||||
|
*
|
||||||
|
* @param {DOMElement} el
|
||||||
|
* @see {@link http://docs.videojs.com/Button.html#buildCSSClass|Button#buildCSSClass}
|
||||||
|
*/
|
||||||
|
buildCSSClass() {
|
||||||
|
return 'vjs-airplay-button ' + super.buildCSSClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrides Button#handleClick to handle button click events. AirPlay
|
||||||
|
* functionality is handled outside of this class, which should be limited
|
||||||
|
* to UI related logic. This function simply triggers an event on the player.
|
||||||
|
*
|
||||||
|
* @fires AirPlayButton#airPlayRequested
|
||||||
|
* @param {DOMElement} el
|
||||||
|
* @see {@link http://docs.videojs.com/Button.html#handleClick|Button#handleClick}
|
||||||
|
*/
|
||||||
|
handleClick() {
|
||||||
|
this.player().trigger('airPlayRequested');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the underlying DOMElement used by the player.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {DOMElement} either an <audio> or <video> tag, depending on the type of
|
||||||
|
* player
|
||||||
|
*/
|
||||||
|
_getMediaEl() {
|
||||||
|
var playerEl = this.player().el();
|
||||||
|
return playerEl.querySelector('video, audio');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Binds a listener to the `webkitplaybacktargetavailabilitychanged` event, if it is
|
||||||
|
* supported, that will show or hide this button Component based on the availability
|
||||||
|
* of the AirPlay function.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_reactToAirPlayAvailableEvents() {
|
||||||
|
var mediaEl = this._getMediaEl(),
|
||||||
|
self = this;
|
||||||
|
if (!mediaEl || !hasAirPlayAPISupport()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
function onTargetAvailabilityChanged(event) {
|
||||||
|
if (event.availability === 'available') {
|
||||||
|
self.show();
|
||||||
|
} else {
|
||||||
|
self.hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mediaEl.addEventListener('webkitplaybacktargetavailabilitychanged', onTargetAvailabilityChanged);
|
||||||
|
this.on('dispose', function () {
|
||||||
|
mediaEl.removeEventListener('webkitplaybacktargetavailabilitychanged', onTargetAvailabilityChanged);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
videojs.registerComponent('airPlayButton', AirPlayButton);
|
||||||
|
};
|
||||||
|
|
||||||
|
},{"../lib/hasAirPlayAPISupport":4}],2:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module enableAirPlay
|
||||||
|
*/
|
||||||
|
|
||||||
|
var hasAirPlayAPISupport = require('./lib/hasAirPlayAPISupport');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {object} the Video.js Player instance
|
||||||
|
* @returns {AirPlayButton} or `undefined` if it does not exist
|
||||||
|
*/
|
||||||
|
function getExistingAirPlayButton(player) {
|
||||||
|
return player.controlBar.getChild('airPlayButton');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds the AirPlayButton Component to the player's ControlBar component, if the
|
||||||
|
* AirPlayButton does not already exist in the ControlBar.
|
||||||
|
* @private
|
||||||
|
* @param player {object} the Video.js Player instance
|
||||||
|
* @param options {object}
|
||||||
|
*/
|
||||||
|
function ensureAirPlayButtonExists(player, options) {
|
||||||
|
var existingAirPlayButton = getExistingAirPlayButton(player),
|
||||||
|
indexOpt;
|
||||||
|
if (options.addButtonToControlBar && !existingAirPlayButton) {
|
||||||
|
// Figure out AirPlay button's index
|
||||||
|
indexOpt = player.controlBar.children().length;
|
||||||
|
if (typeof options.buttonPositionIndex !== 'undefined') {
|
||||||
|
indexOpt = options.buttonPositionIndex >= 0 ? options.buttonPositionIndex : player.controlBar.children().length + options.buttonPositionIndex;
|
||||||
|
}
|
||||||
|
player.controlBar.addChild('airPlayButton', options, indexOpt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles requests for AirPlay triggered by the AirPlayButton Component.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param player {object} the Video.js Player instance
|
||||||
|
*/
|
||||||
|
function onAirPlayRequested(player) {
|
||||||
|
var mediaEl = player.el().querySelector('video, audio');
|
||||||
|
if (mediaEl && mediaEl.webkitShowPlaybackTargetPicker) {
|
||||||
|
mediaEl.webkitShowPlaybackTargetPicker();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds an event listener for the `airPlayRequested` event triggered by the AirPlayButton
|
||||||
|
* Component.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param player {object} the Video.js Player instance
|
||||||
|
*/
|
||||||
|
function listenForAirPlayEvents(player) {
|
||||||
|
// Respond to requests for AirPlay. The AirPlayButton component triggers this event
|
||||||
|
// when the user clicks the AirPlay button.
|
||||||
|
player.on('airPlayRequested', onAirPlayRequested.bind(null, player));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up the AirPlay plugin.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param player {object} the Video.js player
|
||||||
|
* @param options {object} the plugin options
|
||||||
|
*/
|
||||||
|
function enableAirPlay(player, options) {
|
||||||
|
if (!player.controlBar) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (hasAirPlayAPISupport()) {
|
||||||
|
listenForAirPlayEvents(player);
|
||||||
|
ensureAirPlayButtonExists(player, options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the AirPlay plugin with Video.js. Calls
|
||||||
|
* {@link http://docs.videojs.com/module-videojs.html#~registerPlugin|videojs#registerPlugin},
|
||||||
|
* which will add a plugin function called `airPlay` to any instance of a Video.js player
|
||||||
|
* that is created after calling this function. Call `player.airPlay(options)`, passing in
|
||||||
|
* configuration options, to enable the AirPlay plugin on your Player instance.
|
||||||
|
*
|
||||||
|
* Currently, the only configuration option is:
|
||||||
|
*
|
||||||
|
* * **buttonText** - the text to display inside of the button component. By default,
|
||||||
|
* this text is hidden and is used for accessibility purposes.
|
||||||
|
*
|
||||||
|
* @param {object} videojs
|
||||||
|
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin
|
||||||
|
*/
|
||||||
|
module.exports = function (videojs) {
|
||||||
|
videojs.registerPlugin('airPlay', function (options) {
|
||||||
|
var pluginOptions = Object.assign({
|
||||||
|
addButtonToControlBar: true
|
||||||
|
}, options || {});
|
||||||
|
|
||||||
|
// `this` is an instance of a Video.js Player.
|
||||||
|
// Wait until the player is "ready" so that the player's control bar component has
|
||||||
|
// been created.
|
||||||
|
this.ready(enableAirPlay.bind(this, this, pluginOptions));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
},{"./lib/hasAirPlayAPISupport":4}],3:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
var createAirPlayButton = require('./components/AirPlayButton'),
|
||||||
|
createAirPlayPlugin = require('./enableAirPlay');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module index
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers the AirPlay plugin and AirPlayButton Component with Video.js. See
|
||||||
|
* {@link module:AirPlayButton} and {@link module:enableAirPlay} for more details about
|
||||||
|
* how the plugin and button are registered and configured.
|
||||||
|
*
|
||||||
|
* @param {object} videojs
|
||||||
|
* @see module:enableAirPlay
|
||||||
|
* @see module:AirPlayButton
|
||||||
|
*/
|
||||||
|
module.exports = function (videojs) {
|
||||||
|
videojs = videojs || window.videojs;
|
||||||
|
createAirPlayButton(videojs);
|
||||||
|
createAirPlayPlugin(videojs);
|
||||||
|
};
|
||||||
|
|
||||||
|
},{"./components/AirPlayButton":1,"./enableAirPlay":2}],4:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module hasAirPlayAPISupport
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the current browser environment supports AirPlay.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @returns {boolean} true if AirPlay support is available
|
||||||
|
*/
|
||||||
|
module.exports = function () {
|
||||||
|
return !!window.WebKitPlaybackTargetAvailabilityEvent;
|
||||||
|
};
|
||||||
|
|
||||||
|
},{}],5:[function(require,module,exports){
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This module is used as an entry point for the build system to bundle this plugin into a
|
||||||
|
* single javascript file that can be loaded by a script tag on a web page. The javascript
|
||||||
|
* file that is built assumes that `videojs` is available globally at `window.videojs`, so
|
||||||
|
* Video.js must be loaded **before** this plugin is loaded.
|
||||||
|
*
|
||||||
|
* Run `npm install` and then `grunt build` to build the plugin's bundled javascript
|
||||||
|
* file, as well as the CSS and image assets into the project's `./dist/` folder.
|
||||||
|
*
|
||||||
|
* @module standalone
|
||||||
|
*/
|
||||||
|
|
||||||
|
require('./index')();
|
||||||
|
|
||||||
|
},{"./index":3}]},{},[5]);
|
||||||
1
public/_player/videojs/dist/videojs-airplay.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-airplay-button-label{flex-grow:1;margin-left:4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}
|
||||||
2
public/_player/videojs/dist/videojs-airplay.min.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
/*! @silvermine/videojs-airplay 2024-09-06 v1.3.0-8-gdbf8f7d */
|
||||||
|
!function n(a,r,o){function l(i,t){if(!r[i]){if(!a[i]){var e="function"==typeof require&&require;if(!t&&e)return e(i,!0);if(s)return s(i,!0);throw(t=new Error("Cannot find module '"+i+"'")).code="MODULE_NOT_FOUND",t}e=r[i]={exports:{}},a[i][0].call(e.exports,function(t){return l(a[i][1][t]||t)},e,e.exports,n,a,r,o)}return r[i].exports}for(var s="function"==typeof require&&require,t=0;t<o.length;t++)l(o[t]);return l}({1:[function(t,i,e){var n=t("../lib/hasAirPlayAPISupport");i.exports=function(t){var i=t.getComponent("Button");class e extends i{constructor(t,i){super(t,i),n()||this.hide(),this._reactToAirPlayAvailableEvents(),i.addAirPlayLabelToButton?(this.el().classList.add("vjs-airplay-button-lg"),this._labelEl=document.createElement("span"),this._labelEl.classList.add("vjs-airplay-button-label"),this._labelEl.textContent=this.localize("AirPlay"),this.el().appendChild(this._labelEl)):this.controlText("Start AirPlay")}buildCSSClass(){return"vjs-airplay-button "+super.buildCSSClass()}handleClick(){this.player().trigger("airPlayRequested")}_getMediaEl(){return this.player().el().querySelector("video, audio")}_reactToAirPlayAvailableEvents(){var t=this._getMediaEl(),i=this;function e(t){"available"===t.availability?i.show():i.hide()}t&&n()&&(t.addEventListener("webkitplaybacktargetavailabilitychanged",e),this.on("dispose",function(){t.removeEventListener("webkitplaybacktargetavailabilitychanged",e)}))}}t.registerComponent("airPlayButton",e)}},{"../lib/hasAirPlayAPISupport":4}],2:[function(t,i,e){var n=t("./lib/hasAirPlayAPISupport");function a(t){t.on("airPlayRequested",function(t){(t=t.el().querySelector("video, audio"))&&t.webkitShowPlaybackTargetPicker&&t.webkitShowPlaybackTargetPicker()}.bind(null,t))}i.exports=function(t){t.registerPlugin("airPlay",function(t){t=Object.assign({addButtonToControlBar:!0},t||{});this.ready(function(t,i){var e;t.controlBar&&n()&&(a(t),i=i,e=(t=t).controlBar.getChild("airPlayButton"),i.addButtonToControlBar)&&!e&&(e=t.controlBar.children().length,void 0!==i.buttonPositionIndex&&(e=0<=i.buttonPositionIndex?i.buttonPositionIndex:t.controlBar.children().length+i.buttonPositionIndex),t.controlBar.addChild("airPlayButton",i,e))}.bind(this,this,t))})}},{"./lib/hasAirPlayAPISupport":4}],3:[function(t,i,e){var n=t("./components/AirPlayButton"),a=t("./enableAirPlay");i.exports=function(t){t=t||window.videojs,n(t),a(t)}},{"./components/AirPlayButton":1,"./enableAirPlay":2}],4:[function(t,i,e){i.exports=function(){return!!window.WebKitPlaybackTargetAvailabilityEvent}},{}],5:[function(t,i,e){t("./index")()},{"./index":3}]},{},[5]);
|
||||||
1
public/_player/videojs/dist/videojs-chromecast.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vjs-chromecast-button .vjs-icon-placeholder{background:url("ic_cast_white_24dp.png") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url("ic_cast_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-chromecast-button-label{flex-grow:1;margin-left:4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:" ";display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}
|
||||||
4100
public/_player/videojs/dist/videojs-chromecast.js
vendored
Normal file
1
public/_player/videojs/dist/videojs-chromecast.min.css
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.vjs-chromecast-button .vjs-icon-placeholder{background:url("ic_cast_white_24dp.png") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url("ic_cast_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-chromecast-button-label{flex-grow:1;margin-left:4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:" ";display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}
|
||||||
2
public/_player/videojs/dist/videojs-chromecast.min.js
vendored
Normal file
795
public/_player/videojs/dist/videojs-license.js
vendored
@ -5,3 +5,10 @@ dist/videojs-overlay.min.css
|
|||||||
dist/video-js-skin.min.css
|
dist/video-js-skin.min.css
|
||||||
dist/videojs-license.min.js
|
dist/videojs-license.min.js
|
||||||
dist/videojs-license.min.css
|
dist/videojs-license.min.css
|
||||||
|
dist/videojs-chromecast.min.js
|
||||||
|
dist/videojs-chromecast.min.css
|
||||||
|
dist/ic_cast_connected_white_24dp.png
|
||||||
|
dist/ic_cast_white_24dp.png
|
||||||
|
dist/videojs-airplay.min.js
|
||||||
|
dist/videojs-airplay.min.css
|
||||||
|
dist/ic_airplay_white_24px.svg
|
||||||
|
|||||||
@ -15,11 +15,12 @@
|
|||||||
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
||||||
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
||||||
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
||||||
<style>
|
{{#if airplay}}
|
||||||
.player-poster[data-poster] .poster-background[data-poster] {
|
<link href="player/videojs/dist/videojs-airplay.min.css" rel="stylesheet">
|
||||||
height: initial !important;
|
{{/if}}
|
||||||
}
|
{{#if chromecast}}
|
||||||
</style>
|
<link href="player/videojs/dist/videojs-chromecast.min.css" rel="stylesheet">
|
||||||
|
{{/if}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div style="position:absolute;top:0;right:0;bottom:0;left:0">
|
<div style="position:absolute;top:0;right:0;bottom:0;left:0">
|
||||||
@ -28,6 +29,13 @@
|
|||||||
<script src="player/videojs/dist/video.min.js"></script>
|
<script src="player/videojs/dist/video.min.js"></script>
|
||||||
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
||||||
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
||||||
|
{{#if airplay}}
|
||||||
|
<script src='player/videojs/dist/videojs-airplay.min.js'></script>
|
||||||
|
{{/if}}
|
||||||
|
{{#if chromecast}}
|
||||||
|
<script src='player/videojs/dist/videojs-chromecast.min.js'></script>
|
||||||
|
<script src='https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'></script>
|
||||||
|
{{/if}}
|
||||||
<script>
|
<script>
|
||||||
function getQueryParam(key, defaultValue) {
|
function getQueryParam(key, defaultValue) {
|
||||||
var query = window.location.search.substring(1);
|
var query = window.location.search.substring(1);
|
||||||
@ -74,12 +82,20 @@
|
|||||||
liveui: true,
|
liveui: true,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
|
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||||
plugins: {
|
|
||||||
license: playerConfig.license
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (playerConfig.chromecast) {
|
||||||
|
config.techOrder = ["chromecast", "html5"];
|
||||||
|
config.plugins.chromecast = {
|
||||||
|
receiverApplicationId: 'CC1AD845'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playerConfig.airplay) {
|
||||||
|
config.plugins.airPlay = {};
|
||||||
|
}
|
||||||
|
|
||||||
var player = videojs('player', config);
|
var player = videojs('player', config);
|
||||||
player.ready(function() {
|
player.ready(function() {
|
||||||
if(playerConfig.logo.image.length != 0) {
|
if(playerConfig.logo.image.length != 0) {
|
||||||
@ -113,6 +129,8 @@
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
player.license(playerConfig.license);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (autoplay === true) {
|
if (autoplay === true) {
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
var plugins = [];
|
|
||||||
|
|
||||||
if (statistics == true) {
|
|
||||||
plugins.push(ClapprNerdStats);
|
|
||||||
plugins.push(ClapprStats);
|
|
||||||
}
|
|
||||||
|
|
||||||
var config = {
|
|
||||||
source: playerConfig.source,
|
|
||||||
parentId: '#player',
|
|
||||||
baseUrl: 'clappr/',
|
|
||||||
persistConfig: false,
|
|
||||||
plugins: plugins,
|
|
||||||
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
|
||||||
mediacontrol: {
|
|
||||||
seekbar: playerConfig.color.seekbar,
|
|
||||||
buttons: color,
|
|
||||||
},
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
disableCanAutoPlay: true,
|
|
||||||
autoPlay: autoplay,
|
|
||||||
mimeType: 'application/vnd.apple.mpegurl',
|
|
||||||
actualLiveTime: false,
|
|
||||||
exitFullscreenOnEnd: false,
|
|
||||||
mute: mute,
|
|
||||||
playback: {
|
|
||||||
controls: false,
|
|
||||||
playInline: true,
|
|
||||||
recycleVideo: Clappr.Browser.isMobile,
|
|
||||||
hlsjsConfig: {
|
|
||||||
enableWorker: false,
|
|
||||||
capLevelToPlayerSize: true,
|
|
||||||
capLevelOnFPSDrop: true,
|
|
||||||
maxBufferHole: 1,
|
|
||||||
highBufferWatchdogPeriod: 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hlsPlayback: {
|
|
||||||
preload: false,
|
|
||||||
},
|
|
||||||
visibilityEnableIcon: false,
|
|
||||||
clapprStats: {
|
|
||||||
runEach: 1000,
|
|
||||||
onReport: (metrics) => {},
|
|
||||||
},
|
|
||||||
clapprNerdStats: {
|
|
||||||
shortcut: ['command+shift+s', 'ctrl+shift+s'],
|
|
||||||
iconPosition: 'top-right',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (playerConfig.logo.image.length != 0) {
|
|
||||||
config.watermark = playerConfig.logo.image;
|
|
||||||
config.position = playerConfig.logo.position;
|
|
||||||
|
|
||||||
if (playerConfig.logo.link.length != 0) {
|
|
||||||
config.watermarkLink = playerConfig.logo.link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = new window.Clappr.Player(config);
|
|
||||||
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
|
|
||||||
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster() {
|
|
||||||
posterPlugin.options.poster = playerConfig.poster + '?t=' + String(new Date().getTime());
|
|
||||||
posterPlugin.render();
|
|
||||||
});
|
|
||||||
@ -44,6 +44,12 @@
|
|||||||
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/video-js-skin.min.css" rel="stylesheet">
|
||||||
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/videojs-overlay.min.css" rel="stylesheet">
|
||||||
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
<link href="player/videojs/dist/videojs-license.min.css" rel="stylesheet">
|
||||||
|
{{#if airplay}}
|
||||||
|
<link href="player/videojs/dist/videojs-airplay.min.css" rel="stylesheet">
|
||||||
|
{{/if}}
|
||||||
|
{{#if chromecast}}
|
||||||
|
<link href="player/videojs/dist/videojs-chromecast.min.css" rel="stylesheet">
|
||||||
|
{{/if}}
|
||||||
{{/ifEquals}}
|
{{/ifEquals}}
|
||||||
<style>
|
<style>
|
||||||
/** flexboxgrid 6.3.1 **/
|
/** flexboxgrid 6.3.1 **/
|
||||||
@ -373,7 +379,7 @@
|
|||||||
<div class="col-xs-12 player-l2">
|
<div class="col-xs-12 player-l2">
|
||||||
<div class="player-l3">
|
<div class="player-l3">
|
||||||
{{#ifEquals player "videojs"}}
|
{{#ifEquals player "videojs"}}
|
||||||
<video id="player" class="vjs-public video-js player-l4" playsinline></video>
|
<video id="player" class="vjs-public video-js vjs-16-9 player-l4" playsinline></video>
|
||||||
{{else}}
|
{{else}}
|
||||||
<div id="player" class="player-l4"></div>
|
<div id="player" class="player-l4"></div>
|
||||||
{{/ifEquals}}
|
{{/ifEquals}}
|
||||||
@ -432,11 +438,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 channel-list-inside">
|
<div class="col-xs-12 channel-list-inside">
|
||||||
{{#each channels}}
|
{{#each channels}}
|
||||||
<a class="channel-list-link" href="/playersite_{{this.channelid}}.html" target="_self">
|
<a class="channel-list-link" href="playersite_{{this.channelid}}.html" target="_self">
|
||||||
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
|
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
|
||||||
<div class="col-xs-12 middle-xs channel-list-l2">
|
<div class="col-xs-12 middle-xs channel-list-l2">
|
||||||
<span id="channel_list_badge_{{this.channelid}}_1" class="channel-list-badge">LIVE</span>
|
<span id="channel_list_badge_{{this.channelid}}_1" class="channel-list-badge">LIVE</span>
|
||||||
<img src="/memfs/{{this.channelid}}.jpg" class="channel-list-image">
|
<img src="memfs/{{this.channelid}}.jpg" class="channel-list-image">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="channel-list-description">
|
<div class="channel-list-description">
|
||||||
{{this.name}}
|
{{this.name}}
|
||||||
@ -492,11 +498,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-xs-12 col-sm-12">
|
<div class="col-xs-12 col-sm-12">
|
||||||
{{#each channels}}
|
{{#each channels}}
|
||||||
<a class="channel-list-link" href="/playersite_{{this.channelid}}.html" target="_self">
|
<a class="channel-list-link" href="playersite_{{this.channelid}}.html" target="_self">
|
||||||
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
|
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
|
||||||
<div class="col-xs-12 middle-xs channel-list-l2">
|
<div class="col-xs-12 middle-xs channel-list-l2">
|
||||||
<span id="channel_list_badge_{{this.channelid}}_2" class="channel-list-badge">LIVE</span>
|
<span id="channel_list_badge_{{this.channelid}}_2" class="channel-list-badge">LIVE</span>
|
||||||
<img src="/memfs/{{this.channelid}}.jpg" class="channel-list-image">
|
<img src="memfs/{{this.channelid}}.jpg" class="channel-list-image">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="channel-list-description">
|
<div class="channel-list-description">
|
||||||
{{this.name}}
|
{{this.name}}
|
||||||
@ -613,6 +619,7 @@
|
|||||||
{{/if}}{{/if}}
|
{{/if}}{{/if}}
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
{{#if share}}
|
||||||
var modal_share = document.getElementById("modal-share");
|
var modal_share = document.getElementById("modal-share");
|
||||||
var btn_share = document.getElementById("btn-share");
|
var btn_share = document.getElementById("btn-share");
|
||||||
var close_share = document.getElementById("close-share");
|
var close_share = document.getElementById("close-share");
|
||||||
@ -622,6 +629,7 @@
|
|||||||
close_share.onclick = function() {
|
close_share.onclick = function() {
|
||||||
modal_share.style.display = "none";
|
modal_share.style.display = "none";
|
||||||
}
|
}
|
||||||
|
{{/if}}
|
||||||
{{#if imprint_html}}
|
{{#if imprint_html}}
|
||||||
var modal_imprint = document.getElementById("modal-imprint");
|
var modal_imprint = document.getElementById("modal-imprint");
|
||||||
var btn_imprint = document.getElementById("btn-imprint");
|
var btn_imprint = document.getElementById("btn-imprint");
|
||||||
@ -658,7 +666,9 @@
|
|||||||
var close = document.getElementsByClassName("close")[0];
|
var close = document.getElementsByClassName("close")[0];
|
||||||
|
|
||||||
close.onclick = function() {
|
close.onclick = function() {
|
||||||
|
{{#if share}}
|
||||||
modal_share.style.display = "none";
|
modal_share.style.display = "none";
|
||||||
|
{{/if}}
|
||||||
{{#if imprint_html}}
|
{{#if imprint_html}}
|
||||||
modal_imprint.style.display = "none";
|
modal_imprint.style.display = "none";
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@ -670,20 +680,29 @@
|
|||||||
{{/if}}{{/if}}
|
{{/if}}{{/if}}
|
||||||
}
|
}
|
||||||
window.onclick = function(event) {
|
window.onclick = function(event) {
|
||||||
if (event.target == modal_share) {
|
switch(event.target) {
|
||||||
modal_share.style.display = "none";
|
{{#if share}}
|
||||||
{{#if imprint_html}}
|
case modal_share:
|
||||||
} else if (event.target == modal_imprint) {
|
modal_share.style.display = "none";
|
||||||
modal_imprint.style.display = "none";
|
break;
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if terms_html}}
|
{{#if imprint_html}}
|
||||||
} else if (event.target == modal_terms) {
|
case modal_imprint:
|
||||||
modal_terms.style.display = "none";
|
modal_imprint.style.display = "none";
|
||||||
{{/if}}
|
break;
|
||||||
{{#if channel_creator_name}}{{#if channel_creator_description_html}}
|
{{/if}}
|
||||||
} else if (event.target == modal_creator) {
|
{{#if terms_html}}
|
||||||
modal_creator.style.display = "none";
|
case modal_terms:
|
||||||
{{/if}}{{/if}}
|
modal_terms.style.display = "none";
|
||||||
|
break;
|
||||||
|
{{/if}}
|
||||||
|
{{#if channel_creator_name}}{{#if channel_creator_description_html}}
|
||||||
|
case modal_creator:
|
||||||
|
modal_creator.style.display = "none";
|
||||||
|
break;
|
||||||
|
{{/if}}{{/if}}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
@ -692,10 +711,10 @@
|
|||||||
var license = '{{channel_license}}';
|
var license = '{{channel_license}}';
|
||||||
var license_url = '';
|
var license_url = '';
|
||||||
var license_name = 'unknown';
|
var license_name = 'unknown';
|
||||||
if (license === 'CC0 4.0') {
|
if (license === 'CC0 1.0') {
|
||||||
license_name = 'CC0 1.0 Universal'
|
license_name = 'CC0 1.0 Universal'
|
||||||
license_url = 'https://creativecommons.org/publicdomain/zero/1.0/';
|
license_url = 'https://creativecommons.org/publicdomain/zero/1.0/';
|
||||||
license_image = 'https://creativecommons.org/publicdomain/zero/1.0/';
|
license_image = 'https://mirrors.creativecommons.org/presskit/buttons/88x31/png/pd.png';
|
||||||
} else if (license === 'CC BY 4.0') {
|
} else if (license === 'CC BY 4.0') {
|
||||||
license_name = 'CC BY 4.0'
|
license_name = 'CC BY 4.0'
|
||||||
license_url = 'https://creativecommons.org/licenses/by/4.0/';
|
license_url = 'https://creativecommons.org/licenses/by/4.0/';
|
||||||
@ -719,7 +738,7 @@
|
|||||||
} else if (license === 'CC BY-NC-ND 4.0') {
|
} else if (license === 'CC BY-NC-ND 4.0') {
|
||||||
license_name = 'CC BY-NC-ND 4.0';
|
license_name = 'CC BY-NC-ND 4.0';
|
||||||
license_url = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
|
license_url = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
|
||||||
license_image = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
|
license_image = 'https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-nd.png';
|
||||||
}
|
}
|
||||||
document.getElementById("license").setAttribute("href", license_url);
|
document.getElementById("license").setAttribute("href", license_url);
|
||||||
document.getElementById("license_image").src = license_image;
|
document.getElementById("license_image").src = license_image;
|
||||||
@ -782,6 +801,13 @@
|
|||||||
<script src="player/videojs/dist/video.min.js"></script>
|
<script src="player/videojs/dist/video.min.js"></script>
|
||||||
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
<script src="player/videojs/dist/videojs-overlay.min.js"></script>
|
||||||
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
<script src="player/videojs/dist/videojs-license.min.js"></script>
|
||||||
|
{{#if airplay}}
|
||||||
|
<script src='player/videojs/dist/videojs-airplay.min.js'></script>
|
||||||
|
{{/if}}
|
||||||
|
{{#if chromecast}}
|
||||||
|
<script src='player/videojs/dist/videojs-chromecast.min.js'></script>
|
||||||
|
<script src='https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'></script>
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<script src="player/clappr/clappr.min.js"></script>
|
<script src="player/clappr/clappr.min.js"></script>
|
||||||
<script src="player/clappr/clappr-stats.min.js"></script>
|
<script src="player/clappr/clappr-stats.min.js"></script>
|
||||||
@ -824,6 +850,8 @@
|
|||||||
var mute = convertBoolParam("mute", playerConfig.mute);
|
var mute = convertBoolParam("mute", playerConfig.mute);
|
||||||
var statistics = convertBoolParam("stats", playerConfig.statistics);
|
var statistics = convertBoolParam("stats", playerConfig.statistics);
|
||||||
var color = convertColorParam("color", playerConfig.color.buttons);
|
var color = convertColorParam("color", playerConfig.color.buttons);
|
||||||
|
var chromecast = {{#if chromecast}}true{{else}}false{{/if}};
|
||||||
|
var airplay = {{#if airplay}}true{{else}}false{{/if}};
|
||||||
</script>
|
</script>
|
||||||
<script src="playersite/player.js"></script>
|
<script src="playersite/player.js"></script>
|
||||||
|
|
||||||
|
|||||||
@ -2,18 +2,36 @@ var config = {
|
|||||||
controls: true,
|
controls: true,
|
||||||
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
||||||
autoplay: autoplay ? 'muted' : false,
|
autoplay: autoplay ? 'muted' : false,
|
||||||
muted: mute,
|
muted: true,
|
||||||
liveui: true,
|
liveui: true,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
|
// Needed to append the url origin in order for the source to properly pass to the cast device
|
||||||
plugins: {
|
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||||
license: playerConfig.license,
|
plugins: {},
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (chromecast) {
|
||||||
|
config.techOrder = ['chromecast', 'html5'];
|
||||||
|
// Provide a default reciever application ID
|
||||||
|
config.plugins.chromecast = {
|
||||||
|
receiverApplicationId: 'CC1AD845',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
var player = videojs('player', config);
|
var player = videojs('player', config);
|
||||||
|
|
||||||
player.ready(function () {
|
player.ready(function () {
|
||||||
|
if (chromecast) {
|
||||||
|
player.chromecast();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (airplay) {
|
||||||
|
player.airPlay();
|
||||||
|
}
|
||||||
|
|
||||||
|
player.license(playerConfig.license);
|
||||||
|
|
||||||
if (playerConfig.logo.image.length != 0) {
|
if (playerConfig.logo.image.length != 0) {
|
||||||
var overlay = null;
|
var overlay = null;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link rel="icon" href="favicon.ico" />
|
||||||
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#282728" />
|
||||||
<meta name="description" content="Restreamer – Video-Streaming" />
|
<meta name="description" content="Restreamer – Video-Streaming" />
|
||||||
<link rel="apple-touch-icon" href="logo192.png" />
|
<link rel="apple-touch-icon" href="logo192.png" />
|
||||||
<link rel="manifest" href="manifest.json" />
|
<link rel="manifest" href="manifest.json" />
|
||||||
|
|||||||
@ -20,6 +20,6 @@
|
|||||||
],
|
],
|
||||||
"start_url": ".",
|
"start_url": ".",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#282728",
|
||||||
"background_color": "#ffffff"
|
"background_color": "#282728"
|
||||||
}
|
}
|
||||||
|
|||||||
54
src/I18n.js
@ -2,45 +2,63 @@ import React from 'react';
|
|||||||
|
|
||||||
import { I18nProvider } from '@lingui/react';
|
import { I18nProvider } from '@lingui/react';
|
||||||
import { i18n } from '@lingui/core';
|
import { i18n } from '@lingui/core';
|
||||||
import * as plurals from 'make-plural/plurals';
|
|
||||||
|
|
||||||
import { messages as EN } from './locales/en/messages.js';
|
import { messages as EN } from './locales/en/messages.js';
|
||||||
|
import { messages as DA } from './locales/da/messages.js';
|
||||||
import { messages as DE } from './locales/de/messages.js';
|
import { messages as DE } from './locales/de/messages.js';
|
||||||
|
import { messages as EL } from './locales/el/messages.js';
|
||||||
import { messages as ES } from './locales/es/messages.js';
|
import { messages as ES } from './locales/es/messages.js';
|
||||||
import { messages as FR } from './locales/fr/messages.js';
|
import { messages as FR } from './locales/fr/messages.js';
|
||||||
import { messages as IT } from './locales/it/messages.js';
|
import { messages as IT } from './locales/it/messages.js';
|
||||||
|
import { messages as KO } from './locales/ko/messages.js';
|
||||||
import { messages as PL } from './locales/pl/messages.js';
|
import { messages as PL } from './locales/pl/messages.js';
|
||||||
import { messages as PT } from './locales/pt/messages.js';
|
import { messages as PT } from './locales/pt-br/messages.js';
|
||||||
import { messages as RU } from './locales/ru/messages.js';
|
import { messages as RU } from './locales/ru/messages.js';
|
||||||
|
import { messages as SL } from './locales/sl/messages.js';
|
||||||
|
import { messages as TR } from './locales/tr/messages.js';
|
||||||
|
import { messages as UK } from './locales/uk/messages.js';
|
||||||
|
import { messages as ZH } from './locales/zh-hans/messages.js';
|
||||||
import * as Storage from './utils/storage';
|
import * as Storage from './utils/storage';
|
||||||
|
|
||||||
i18n.loadLocaleData('en', { plurals: plurals.en });
|
|
||||||
i18n.loadLocaleData('de', { plurals: plurals.de });
|
|
||||||
i18n.loadLocaleData('es', { plurals: plurals.es });
|
|
||||||
i18n.loadLocaleData('fr', { plurals: plurals.fr });
|
|
||||||
i18n.loadLocaleData('it', { plurals: plurals.it });
|
|
||||||
i18n.loadLocaleData('pl', { plurals: plurals.pl });
|
|
||||||
i18n.loadLocaleData('pt', { plurals: plurals.pt });
|
|
||||||
i18n.loadLocaleData('ru', { plurals: plurals.ru });
|
|
||||||
i18n.load({
|
i18n.load({
|
||||||
en: EN,
|
en: EN,
|
||||||
|
da: DA,
|
||||||
de: DE,
|
de: DE,
|
||||||
|
el: EL,
|
||||||
es: ES,
|
es: ES,
|
||||||
fr: FR,
|
fr: FR,
|
||||||
it: IT,
|
it: IT,
|
||||||
|
ko: KO,
|
||||||
pl: PL,
|
pl: PL,
|
||||||
pt: PT,
|
'pt-br': PT,
|
||||||
ru: RU,
|
ru: RU,
|
||||||
|
sl: SL,
|
||||||
|
tr: TR,
|
||||||
|
uk: UK,
|
||||||
|
'zh-hans': ZH,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
const aliases = {
|
||||||
let lang = Storage.Get('language');
|
pt: 'pt-br',
|
||||||
if (supportedLanguages.indexOf(lang) === -1) {
|
'zh-cn': 'zh-hans',
|
||||||
lang = getBrowserLanguage(defaultLanguage);
|
};
|
||||||
|
|
||||||
|
const getAlias = (lang) => {
|
||||||
|
if (lang in aliases) {
|
||||||
|
return aliases[lang];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return lang;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||||
|
let lang = getAlias(Storage.Get('language'));
|
||||||
if (supportedLanguages.indexOf(lang) === -1) {
|
if (supportedLanguages.indexOf(lang) === -1) {
|
||||||
lang = defaultLanguage;
|
lang = getAlias(getBrowserLanguage(defaultLanguage));
|
||||||
|
|
||||||
|
if (supportedLanguages.indexOf(lang) === -1) {
|
||||||
|
lang = defaultLanguage;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Storage.Set('language', lang);
|
Storage.Set('language', lang);
|
||||||
@ -51,7 +69,7 @@ const getLanguage = (defaultLanguage, supportedLanguages) => {
|
|||||||
const getBrowserLanguage = (defaultLanguage) => {
|
const getBrowserLanguage = (defaultLanguage) => {
|
||||||
let lang = window.navigator.language;
|
let lang = window.navigator.language;
|
||||||
|
|
||||||
const match = lang.match(/^[a-z]+/);
|
const match = lang.match(/^[a-z]+(-[a-z]+)?/i);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return defaultLanguage;
|
return defaultLanguage;
|
||||||
}
|
}
|
||||||
@ -59,7 +77,7 @@ const getBrowserLanguage = (defaultLanguage) => {
|
|||||||
return match[0].toLowerCase();
|
return match[0].toLowerCase();
|
||||||
};
|
};
|
||||||
|
|
||||||
i18n.activate(getLanguage('en', ['en', 'de', 'es', 'fr', 'it', 'pl', 'pt', 'ru']));
|
i18n.activate(getLanguage('en', ['en', 'da', 'de', 'el', 'es', 'fr', 'it', 'ko', 'pl', 'pt-br', 'ru', 'sl', 'tr', 'uk', 'zh-hans']));
|
||||||
|
|
||||||
export default function Provider(props) {
|
export default function Provider(props) {
|
||||||
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
||||||
|
|||||||
@ -14,9 +14,15 @@ import ChannelList from './misc/ChannelList';
|
|||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import I18n from './I18n';
|
import I18n from './I18n';
|
||||||
import Header from './Header';
|
import Header from './Header';
|
||||||
|
import * as M from './utils/metadata';
|
||||||
import Restreamer from './utils/restreamer';
|
import Restreamer from './utils/restreamer';
|
||||||
import Router from './Router';
|
import Router from './Router';
|
||||||
import Views from './views';
|
import Views from './views';
|
||||||
|
import { UI as Version } from './version';
|
||||||
|
import Changelog from './misc/Changelog';
|
||||||
|
|
||||||
|
import SemverGt from 'semver/functions/gt';
|
||||||
|
import SemverValid from 'semver/functions/valid';
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
MainHeader: {
|
MainHeader: {
|
||||||
@ -58,6 +64,12 @@ export default function RestreamerUI(props) {
|
|||||||
channelid: '',
|
channelid: '',
|
||||||
channels: [],
|
channels: [],
|
||||||
});
|
});
|
||||||
|
const [$metadata, setMetadata] = React.useState({});
|
||||||
|
const [$changelog, setChangelog] = React.useState({
|
||||||
|
open: false,
|
||||||
|
current: '',
|
||||||
|
previous: '',
|
||||||
|
});
|
||||||
|
|
||||||
const restreamer = React.useRef(null);
|
const restreamer = React.useRef(null);
|
||||||
|
|
||||||
@ -131,6 +143,8 @@ export default function RestreamerUI(props) {
|
|||||||
|
|
||||||
const valid = await restreamer.current.Validate();
|
const valid = await restreamer.current.Validate();
|
||||||
|
|
||||||
|
await checkChangelog();
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
...$state,
|
...$state,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
@ -146,8 +160,93 @@ export default function RestreamerUI(props) {
|
|||||||
setReady(true);
|
setReady(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const checkChangelog = async () => {
|
||||||
|
let showChangelog = true;
|
||||||
|
|
||||||
|
if (restreamer.current.IsConnected() === true) {
|
||||||
|
let metadata = await restreamer.current.GetMetadata(false);
|
||||||
|
const channels = await restreamer.current.ListChannels();
|
||||||
|
let current = Version.replace('restreamer-', '');
|
||||||
|
let previous = '';
|
||||||
|
|
||||||
|
if (SemverValid(current) === null) {
|
||||||
|
showChangelog = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadata === null) {
|
||||||
|
if (channels.length === 1) {
|
||||||
|
const progress = await restreamer.current.GetIngestProgress(channels[0].channelid);
|
||||||
|
if (progress.valid === false) {
|
||||||
|
// assume fresh installation
|
||||||
|
metadata = M.initMetadata(metadata);
|
||||||
|
await restreamer.current.SetMetadata({
|
||||||
|
...metadata,
|
||||||
|
bundle: {
|
||||||
|
...metadata.bundle,
|
||||||
|
version: current,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata = M.initMetadata(metadata);
|
||||||
|
|
||||||
|
if ('version' in metadata.bundle) {
|
||||||
|
if (SemverValid(metadata.bundle.version) !== null) {
|
||||||
|
previous = metadata.bundle.version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showChangelog === true) {
|
||||||
|
if (SemverValid(previous) === null) {
|
||||||
|
previous = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous.length !== 0) {
|
||||||
|
if (!SemverGt(current, previous)) {
|
||||||
|
showChangelog = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMetadata({
|
||||||
|
...$metadata,
|
||||||
|
...metadata,
|
||||||
|
});
|
||||||
|
|
||||||
|
setChangelog({
|
||||||
|
...$changelog,
|
||||||
|
open: showChangelog,
|
||||||
|
current: current,
|
||||||
|
previous: previous,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return showChangelog;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseChangelog = async () => {
|
||||||
|
await restreamer.current.SetMetadata({
|
||||||
|
...$metadata,
|
||||||
|
bundle: {
|
||||||
|
...$metadata.bundle,
|
||||||
|
version: $changelog.current,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
setChangelog({
|
||||||
|
...$changelog,
|
||||||
|
open: false,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogin = async (username, password) => {
|
const handleLogin = async (username, password) => {
|
||||||
const connected = await restreamer.current.Login(username, password);
|
const connected = await restreamer.current.Login(username, password);
|
||||||
|
|
||||||
|
await checkChangelog();
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
...$state,
|
...$state,
|
||||||
connected: connected,
|
connected: connected,
|
||||||
@ -196,16 +295,25 @@ export default function RestreamerUI(props) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordReset = async (username, password) => {
|
const handlePasswordReset = async (username, loginUsername, password, loginPassword) => {
|
||||||
const [, err] = await restreamer.current.ConfigSet({
|
const data = {
|
||||||
api: {
|
api: {
|
||||||
auth: {
|
auth: {
|
||||||
enable: true,
|
enable: true,
|
||||||
username: username,
|
|
||||||
password: password,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
version: 3,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (username.length !== 0) {
|
||||||
|
data.api.auth.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length !== 0) {
|
||||||
|
data.api.auth.password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, err] = await restreamer.current.ConfigSet(data);
|
||||||
if (err !== null) {
|
if (err !== null) {
|
||||||
notify('error', 'save:settings', `There was an error resetting the password.`);
|
notify('error', 'save:settings', `There was an error resetting the password.`);
|
||||||
return 'ERROR';
|
return 'ERROR';
|
||||||
@ -249,7 +357,7 @@ export default function RestreamerUI(props) {
|
|||||||
if (restarted === true) {
|
if (restarted === true) {
|
||||||
// After the restart the API requires a login and this means the restart happened
|
// After the restart the API requires a login and this means the restart happened
|
||||||
await restreamer.current.Validate();
|
await restreamer.current.Validate();
|
||||||
await restreamer.current.Login(username, password);
|
await restreamer.current.Login(loginUsername, loginPassword);
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
} else {
|
} else {
|
||||||
@ -348,30 +456,42 @@ export default function RestreamerUI(props) {
|
|||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
let view = <Views.Initializing />;
|
let view = null;
|
||||||
if ($state.valid === false) {
|
if ($state.initialized === false) {
|
||||||
view = <Views.Invalid address={restreamer.current.Address()} />;
|
view = <Views.Initializing />;
|
||||||
} else if ($state.connected === false) {
|
|
||||||
view = (
|
|
||||||
<Views.Login
|
|
||||||
onLogin={handleLogin}
|
|
||||||
auths={restreamer.current.Auths()}
|
|
||||||
hasService={$state.service}
|
|
||||||
address={restreamer.current.Address()}
|
|
||||||
onAuth0={handleAuth0}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if ($state.compatibility.compatible === false) {
|
|
||||||
if ($state.compatibility.core.compatible === false) {
|
|
||||||
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
|
|
||||||
} else if ($state.compatibility.ffmpeg.compatible === false) {
|
|
||||||
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
|
|
||||||
}
|
|
||||||
} else if ($state.password === true) {
|
|
||||||
view = <Views.Password onReset={handlePasswordReset} />;
|
|
||||||
} else {
|
} else {
|
||||||
view = <Router restreamer={restreamer.current} />;
|
if ($state.valid === false) {
|
||||||
resources = handleResources;
|
view = <Views.Invalid address={restreamer.current.Address()} />;
|
||||||
|
} else if ($state.connected === false) {
|
||||||
|
view = (
|
||||||
|
<Views.Login
|
||||||
|
onLogin={handleLogin}
|
||||||
|
auths={restreamer.current.Auths()}
|
||||||
|
hasService={$state.service}
|
||||||
|
address={restreamer.current.Address()}
|
||||||
|
onAuth0={handleAuth0}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else if ($state.compatibility.compatible === false) {
|
||||||
|
if ($state.compatibility.core.compatible === false) {
|
||||||
|
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
|
||||||
|
} else if ($state.compatibility.ffmpeg.compatible === false) {
|
||||||
|
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
|
||||||
|
}
|
||||||
|
} else if ($state.password === true) {
|
||||||
|
view = (
|
||||||
|
<Views.Password
|
||||||
|
onReset={handlePasswordReset}
|
||||||
|
username={restreamer.current.ConfigValue('api.auth.username')}
|
||||||
|
usernameOverride={restreamer.current.ConfigOverrides('api.auth.username')}
|
||||||
|
password={restreamer.current.ConfigValue('api.auth.password')}
|
||||||
|
passwordOverride={restreamer.current.ConfigOverrides('api.auth.password')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
view = <Router restreamer={restreamer.current} />;
|
||||||
|
resources = handleResources;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const expand = $state.connected && $state.compatibility.compatible && !$state.password;
|
const expand = $state.connected && $state.compatibility.compatible && !$state.password;
|
||||||
@ -428,6 +548,9 @@ export default function RestreamerUI(props) {
|
|||||||
onState={handleStateChannel}
|
onState={handleStateChannel}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{expand && $changelog.open && (
|
||||||
|
<Changelog open={$changelog.open} onClose={handleCloseChangelog} current={$changelog.current} previous={$changelog.previous} />
|
||||||
|
)}
|
||||||
</NotifyProvider>
|
</NotifyProvider>
|
||||||
</I18n>
|
</I18n>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Route, Navigate, Routes, HashRouter } from 'react-router-dom';
|
import { Route, Navigate, Routes, HashRouter as DOMRouter } from 'react-router-dom';
|
||||||
|
|
||||||
import Views from './views';
|
import Views from './views';
|
||||||
|
|
||||||
@ -11,9 +11,9 @@ export default function Router(props) {
|
|||||||
const channelid = props.restreamer.GetCurrentChannelID();
|
const channelid = props.restreamer.GetCurrentChannelID();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HashRouter>
|
<DOMRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Views.ChannelSelect restreamer={props.restreamer} />} />
|
<Route path="/" element={<Views.ChannelSelect channelid={channelid} />} />
|
||||||
<Route path="/playersite" element={<Views.Playersite restreamer={props.restreamer} />} />
|
<Route path="/playersite" element={<Views.Playersite restreamer={props.restreamer} />} />
|
||||||
<Route path="/settings" element={<Views.Settings restreamer={props.restreamer} />} />
|
<Route path="/settings" element={<Views.Settings restreamer={props.restreamer} />} />
|
||||||
<Route path="/settings/:tab" element={<Views.Settings restreamer={props.restreamer} />} />
|
<Route path="/settings/:tab" element={<Views.Settings restreamer={props.restreamer} />} />
|
||||||
@ -26,7 +26,7 @@ export default function Router(props) {
|
|||||||
<Route path="/:channelid/publication/:service/:index" element={<Views.EditService key={channelid} restreamer={props.restreamer} />} />
|
<Route path="/:channelid/publication/:service/:index" element={<Views.EditService key={channelid} restreamer={props.restreamer} />} />
|
||||||
<Route path="*" render={() => <Navigate to="/" replace />} />
|
<Route path="*" render={() => <Navigate to="/" replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</DOMRouter>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
src/index.js
@ -1,5 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import { createRoot } from 'react-dom/client';
|
||||||
|
|
||||||
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
||||||
import '@fontsource/dosis';
|
import '@fontsource/dosis';
|
||||||
@ -10,18 +10,20 @@ import theme from './theme';
|
|||||||
import RestreamerUI from './RestreamerUI';
|
import RestreamerUI from './RestreamerUI';
|
||||||
|
|
||||||
let address = window.location.protocol + '//' + window.location.host;
|
let address = window.location.protocol + '//' + window.location.host;
|
||||||
|
if (window.location.pathname.endsWith('/ui/')) {
|
||||||
|
address += window.location.pathname.replace(/ui\/$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(window.location.search.substring(1));
|
const urlParams = new URLSearchParams(window.location.search.substring(1));
|
||||||
if (urlParams.has('address') === true) {
|
if (urlParams.has('address') === true) {
|
||||||
address = urlParams.get('address');
|
address = urlParams.get('address');
|
||||||
}
|
}
|
||||||
|
|
||||||
ReactDOM.render(
|
createRoot(document.getElementById('root')).render(
|
||||||
<StyledEngineProvider injectFirst>
|
<StyledEngineProvider injectFirst>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<RestreamerUI address={address} />
|
<RestreamerUI address={address} />
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</StyledEngineProvider>,
|
</StyledEngineProvider>
|
||||||
document.getElementById('root')
|
|
||||||
);
|
);
|
||||||
|
|||||||
1
src/locales/da/messages.js
Normal file
3451
src/locales/da/messages.po
Normal file
1
src/locales/el/messages.js
Normal file
3451
src/locales/el/messages.po
Normal file
1
src/locales/ko/messages.js
Normal file
3451
src/locales/ko/messages.po
Normal file
1
src/locales/pt-br/messages.js
Normal file
1
src/locales/sl/messages.js
Normal file
3451
src/locales/sl/messages.po
Normal file
1
src/locales/tr/messages.js
Normal file
3451
src/locales/tr/messages.po
Normal file
1
src/locales/uk/messages.js
Normal file
3451
src/locales/uk/messages.po
Normal file
1
src/locales/zh-hans/messages.js
Normal file
3451
src/locales/zh-hans/messages.po
Normal file
@ -47,8 +47,9 @@ export default function Component(props) {
|
|||||||
return (
|
return (
|
||||||
<Stack
|
<Stack
|
||||||
direction="column"
|
direction="column"
|
||||||
justifyContent="center"
|
justifyContent={props.justifyContent}
|
||||||
alignItems="center"
|
alignItems={props.alignItems}
|
||||||
|
textAlign={props.textAlign}
|
||||||
spacing={1}
|
spacing={1}
|
||||||
className={
|
className={
|
||||||
props.color === 'dark' ? classes.dark : props.color === 'success' ? classes.success : props.color === 'danger' ? classes.danger : classes.light
|
props.color === 'dark' ? classes.dark : props.color === 'success' ? classes.success : props.color === 'danger' ? classes.danger : classes.light
|
||||||
@ -62,4 +63,7 @@ export default function Component(props) {
|
|||||||
|
|
||||||
Component.defaultProps = {
|
Component.defaultProps = {
|
||||||
color: 'light',
|
color: 'light',
|
||||||
|
textAlign: 'left',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
};
|
};
|
||||||
|
|||||||
197
src/misc/Changelog.js
Normal file
@ -0,0 +1,197 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
import makeStyles from '@mui/styles/makeStyles';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import SemverGt from 'semver/functions/gt';
|
||||||
|
import SemverLte from 'semver/functions/lte';
|
||||||
|
import SemverEq from 'semver/functions/eq';
|
||||||
|
import SemverValid from 'semver/functions/valid';
|
||||||
|
|
||||||
|
import BoxText from './BoxText';
|
||||||
|
import Dialog from './modals/Dialog';
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
h1: {
|
||||||
|
fontFamily: theme.typography.h1.fontFamily,
|
||||||
|
fontSize: theme.typography.h1.fontSize,
|
||||||
|
marginTop: '.5rem',
|
||||||
|
marginBottom: '-1rem',
|
||||||
|
},
|
||||||
|
h2: {
|
||||||
|
fontFamily: theme.typography.h2.fontFamily,
|
||||||
|
fontSize: theme.typography.h2.fontSize,
|
||||||
|
paddingTop: '1.5rem',
|
||||||
|
marginBottom: theme.typography.h2.marginBottom,
|
||||||
|
'&::after': {
|
||||||
|
content: '" "',
|
||||||
|
display: 'block',
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: theme.palette.primary.contrastText,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
h3: {
|
||||||
|
fontFamily: theme.typography.h3.fontFamily,
|
||||||
|
fontSize: theme.typography.h3.fontSize,
|
||||||
|
paddingTop: '.5rem',
|
||||||
|
marginBottom: theme.typography.h3.marginBottom,
|
||||||
|
},
|
||||||
|
h4: {
|
||||||
|
fontFamily: theme.typography.h4.fontFamily,
|
||||||
|
fontSize: theme.typography.h4.fontSize,
|
||||||
|
marginBottom: theme.typography.h4.marginBottom,
|
||||||
|
},
|
||||||
|
a: {
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: theme.palette.secondary.main,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default function Changelog(props) {
|
||||||
|
const [$data, setData] = React.useState('');
|
||||||
|
const classes = useStyles();
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
await onMount();
|
||||||
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onMount = async () => {
|
||||||
|
let data = await loadData();
|
||||||
|
data = filter(data, props.current, props.previous);
|
||||||
|
|
||||||
|
setData(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
let response = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
response = await fetch('CHANGELOG.md', {
|
||||||
|
method: 'GET',
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.ok === false) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return await response.text();
|
||||||
|
};
|
||||||
|
|
||||||
|
const filter = (data, current, previous) => {
|
||||||
|
let lines = data.split('\n');
|
||||||
|
let filteredLines = [];
|
||||||
|
|
||||||
|
let copy = true;
|
||||||
|
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
if (lines[i].startsWith('## ')) {
|
||||||
|
let version = lines[i].replace('## ', '');
|
||||||
|
|
||||||
|
if (SemverValid(version) === null) {
|
||||||
|
if (copy === true) {
|
||||||
|
filteredLines.push(lines[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current.length === 0) {
|
||||||
|
current = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previous.length === 0) {
|
||||||
|
previous = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SemverEq(current, previous)) {
|
||||||
|
if (SemverEq(version, current)) {
|
||||||
|
copy = true;
|
||||||
|
} else {
|
||||||
|
copy = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (SemverLte(version, current) && SemverGt(version, previous)) {
|
||||||
|
copy = true;
|
||||||
|
} else {
|
||||||
|
copy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copy === true) {
|
||||||
|
filteredLines.push(lines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredLines.join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
|
if ($data.length === 0 || $data.startsWith('<!DOCTYPE')) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const renderers = {
|
||||||
|
h1: (props) => (
|
||||||
|
<h1 className={classes.h1} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</h1>
|
||||||
|
),
|
||||||
|
h2: (props) => (
|
||||||
|
<h2 className={classes.h2} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</h2>
|
||||||
|
),
|
||||||
|
h3: (props) => (
|
||||||
|
<h3 className={classes.h3} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</h3>
|
||||||
|
),
|
||||||
|
h4: (props) => (
|
||||||
|
<h4 className={classes.h4} {...props}>
|
||||||
|
{props.children}
|
||||||
|
</h4>
|
||||||
|
),
|
||||||
|
a: (props) => (
|
||||||
|
<a className={classes.a} target="_blank" {...props}>
|
||||||
|
{props.children}
|
||||||
|
</a>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog
|
||||||
|
open={props.open}
|
||||||
|
onClose={props.onClose}
|
||||||
|
title={<Trans>Update details (Changelog)</Trans>}
|
||||||
|
maxWidth={600}
|
||||||
|
buttonsRight={
|
||||||
|
<Button variant="outlined" color="primary" onClick={props.onClose}>
|
||||||
|
<Trans>Close</Trans>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<BoxText alignItems="left">
|
||||||
|
<ReactMarkdown components={renderers}>{$data}</ReactMarkdown>
|
||||||
|
</BoxText>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Changelog.defaultProps = {
|
||||||
|
open: false,
|
||||||
|
current: '',
|
||||||
|
previous: '',
|
||||||
|
onClose: () => {},
|
||||||
|
};
|
||||||
@ -10,6 +10,8 @@ import Button from '@mui/material/Button';
|
|||||||
import ButtonBase from '@mui/material/ButtonBase';
|
import ButtonBase from '@mui/material/ButtonBase';
|
||||||
import CloseIcon from '@mui/icons-material/Close';
|
import CloseIcon from '@mui/icons-material/Close';
|
||||||
import DoNotDisturbAltIcon from '@mui/icons-material/DoNotDisturbAlt';
|
import DoNotDisturbAltIcon from '@mui/icons-material/DoNotDisturbAlt';
|
||||||
|
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
||||||
|
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import IconButton from '@mui/material/IconButton';
|
import IconButton from '@mui/material/IconButton';
|
||||||
import LensIcon from '@mui/icons-material/Lens';
|
import LensIcon from '@mui/icons-material/Lens';
|
||||||
@ -119,7 +121,7 @@ const ImageBackdrop = styled('span')(({ theme }) => ({
|
|||||||
border: `2px solid ${theme.palette.primary.dark}`,
|
border: `2px solid ${theme.palette.primary.dark}`,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function ChannelButton(props) {
|
function ChannelButton(props, largeChannelList) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -154,7 +156,7 @@ function ChannelButton(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
<Grid item xs={12} sm={6} md={4} lg={3} style={{ paddingBottom: largeChannelList ? '10px' : 'auto' }}>
|
||||||
<ImageButton focusRipple disabled={props.disabled} onClick={props.onClick} style={{ width: props.width }}>
|
<ImageButton focusRipple disabled={props.disabled} onClick={props.onClick} style={{ width: props.width }}>
|
||||||
<Stack direction="column" spacing={0.5}>
|
<Stack direction="column" spacing={0.5}>
|
||||||
<Image
|
<Image
|
||||||
@ -192,6 +194,21 @@ ChannelButton.defaultProps = {
|
|||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const calculateColumnsPerRow = (breakpointSmall, breakpointMedium, breakpointLarge) => {
|
||||||
|
if (breakpointLarge) {
|
||||||
|
return 4;
|
||||||
|
} else if (breakpointMedium) {
|
||||||
|
return 3;
|
||||||
|
} else if (breakpointSmall) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateRowsToFit = (windowHeight, thumbnailHeight, otherUIHeight) => {
|
||||||
|
return Math.floor((windowHeight - otherUIHeight) / thumbnailHeight);
|
||||||
|
};
|
||||||
|
|
||||||
export default function ChannelList(props) {
|
export default function ChannelList(props) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
@ -205,8 +222,12 @@ export default function ChannelList(props) {
|
|||||||
open: false,
|
open: false,
|
||||||
name: '',
|
name: '',
|
||||||
});
|
});
|
||||||
|
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
|
||||||
|
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
|
||||||
|
|
||||||
const { channels: allChannels, channelid, onClick, onState } = props;
|
const { channels: allChannels, channelid, onClick, onClose, onState } = props;
|
||||||
|
|
||||||
|
const [$largeChannelList, setLargeChannelList] = React.useState(false);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@ -246,19 +267,40 @@ export default function ChannelList(props) {
|
|||||||
disabled={channelid === channel.channelid}
|
disabled={channelid === channel.channelid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onClick(channel.channelid);
|
onClick(channel.channelid);
|
||||||
|
if ($largeChannelList) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
|
largeChannelList={$largeChannelList}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
setChannels(channels);
|
setChannels(channels);
|
||||||
})();
|
})();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [$pos, allChannels, $nChannels, channelid, onClick, onState]);
|
}, [$pos, allChannels, $nChannels, channelid, onClick, onState]);
|
||||||
|
|
||||||
const onMount = async () => {
|
const onMount = async () => {
|
||||||
setPos(0);
|
setPos(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleResize = () => {
|
||||||
|
setWindowWidth(window.innerWidth);
|
||||||
|
setWindowHeight(window.innerHeight);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const columns = calculateColumnsPerRow(breakpointSmall, breakpointMedium, breakpointLarge);
|
||||||
|
const rows = $largeChannelList ? calculateRowsToFit(windowHeight, 200, 60) : 1;
|
||||||
|
setNChannels(rows * columns);
|
||||||
|
}, [breakpointSmall, breakpointMedium, breakpointLarge, windowHeight, windowWidth, $largeChannelList]);
|
||||||
|
|
||||||
const handleAddChannelDialog = () => {
|
const handleAddChannelDialog = () => {
|
||||||
setAddChannel({
|
setAddChannel({
|
||||||
...$addChannel,
|
...$addChannel,
|
||||||
@ -282,6 +324,11 @@ export default function ChannelList(props) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleLargeChannelList = () => {
|
||||||
|
setLargeChannelList(!$largeChannelList);
|
||||||
|
setPos(0);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<SwipeableDrawer
|
<SwipeableDrawer
|
||||||
@ -289,7 +336,13 @@ export default function ChannelList(props) {
|
|||||||
open={props.open}
|
open={props.open}
|
||||||
onOpen={() => {}}
|
onOpen={() => {}}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
sx={{ marginButtom: 60 }}
|
sx={{
|
||||||
|
marginButtom: 60,
|
||||||
|
'& .MuiDrawer-paper': {
|
||||||
|
top: $largeChannelList ? '0px!important' : 'auto!important',
|
||||||
|
height: $largeChannelList ? '100vh' : 'auto',
|
||||||
|
},
|
||||||
|
}}
|
||||||
BackdropProps={{ invisible: true }}
|
BackdropProps={{ invisible: true }}
|
||||||
classes={{
|
classes={{
|
||||||
paper: classes.drawerPaper,
|
paper: classes.drawerPaper,
|
||||||
@ -307,6 +360,9 @@ export default function ChannelList(props) {
|
|||||||
<IconButton color="inherit" size="large" onClick={handleAddChannelDialog}>
|
<IconButton color="inherit" size="large" onClick={handleAddChannelDialog}>
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
<IconButton color="inherit" size="large" onClick={handleLargeChannelList}>
|
||||||
|
{$largeChannelList ? <FullscreenExitIcon /> : <FullscreenIcon />}
|
||||||
|
</IconButton>
|
||||||
<IconButton color="inherit" size="large" onClick={props.onClose}>
|
<IconButton color="inherit" size="large" onClick={props.onClose}>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@ -327,7 +383,7 @@ export default function ChannelList(props) {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Stack>
|
</Stack>
|
||||||
<Stack direction="row" spacing={0} className={classes.channelItems}>
|
<Stack direction="row" spacing={0} className={classes.channelItems}>
|
||||||
<Grid container spacing={0} justifyContent="center">
|
<Grid container spacing={0} justifyContent={$largeChannelList ? 'flex-start' : 'center'}>
|
||||||
{$channels}
|
{$channels}
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|||||||
@ -16,9 +16,19 @@ export default function EncodingSelect(props) {
|
|||||||
const { i18n } = useLingui();
|
const { i18n } = useLingui();
|
||||||
|
|
||||||
const profile = props.profile;
|
const profile = props.profile;
|
||||||
|
let availableEncoders = [];
|
||||||
|
let availableDecoders = [];
|
||||||
|
|
||||||
|
if (props.type === 'video') {
|
||||||
|
availableEncoders = props.skills.encoders.video;
|
||||||
|
availableDecoders = props.skills.decoders.video;
|
||||||
|
} else if (props.type === 'audio') {
|
||||||
|
availableEncoders = props.skills.encoders.audio;
|
||||||
|
}
|
||||||
|
|
||||||
const handleDecoderChange = (event) => {
|
const handleDecoderChange = (event) => {
|
||||||
const decoder = profile.decoder;
|
const decoder = profile.decoder;
|
||||||
|
const stream = props.streams[profile.stream];
|
||||||
decoder.coder = event.target.value;
|
decoder.coder = event.target.value;
|
||||||
|
|
||||||
// If the coder changes, use the coder's default settings
|
// If the coder changes, use the coder's default settings
|
||||||
@ -30,7 +40,7 @@ export default function EncodingSelect(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c !== null) {
|
if (c !== null) {
|
||||||
const defaults = c.defaults();
|
const defaults = c.defaults(stream, props.skills);
|
||||||
decoder.settings = defaults.settings;
|
decoder.settings = defaults.settings;
|
||||||
decoder.mapping = defaults.mapping;
|
decoder.mapping = defaults.mapping;
|
||||||
}
|
}
|
||||||
@ -49,6 +59,7 @@ export default function EncodingSelect(props) {
|
|||||||
|
|
||||||
const handleEncoderChange = (event) => {
|
const handleEncoderChange = (event) => {
|
||||||
const encoder = profile.encoder;
|
const encoder = profile.encoder;
|
||||||
|
const stream = props.streams[profile.stream];
|
||||||
encoder.coder = event.target.value;
|
encoder.coder = event.target.value;
|
||||||
|
|
||||||
// If the coder changes, use the coder's default settings
|
// If the coder changes, use the coder's default settings
|
||||||
@ -60,7 +71,7 @@ export default function EncodingSelect(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (c !== null) {
|
if (c !== null) {
|
||||||
const defaults = c.defaults({});
|
const defaults = c.defaults(stream, props.skills);
|
||||||
encoder.settings = defaults.settings;
|
encoder.settings = defaults.settings;
|
||||||
encoder.mapping = defaults.mapping;
|
encoder.mapping = defaults.mapping;
|
||||||
}
|
}
|
||||||
@ -116,10 +127,10 @@ export default function EncodingSelect(props) {
|
|||||||
let encoderSettingsHelp = null;
|
let encoderSettingsHelp = null;
|
||||||
|
|
||||||
let coder = encoderRegistry.Get(profile.encoder.coder);
|
let coder = encoderRegistry.Get(profile.encoder.coder);
|
||||||
if (coder !== null && props.availableEncoders.includes(coder.coder)) {
|
if (coder !== null && availableEncoders.includes(coder.coder)) {
|
||||||
const Settings = coder.component;
|
const Settings = coder.component;
|
||||||
|
|
||||||
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} onChange={handleEncoderSettingsChange} />;
|
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} skills={props.skills} onChange={handleEncoderSettingsChange} />;
|
||||||
|
|
||||||
if (props.type === 'video' && !['copy', 'none', 'rawvideo'].includes(coder.coder)) {
|
if (props.type === 'video' && !['copy', 'none', 'rawvideo'].includes(coder.coder)) {
|
||||||
encoderSettingsHelp = handleEncoderHelp(coder.coder);
|
encoderSettingsHelp = handleEncoderHelp(coder.coder);
|
||||||
@ -130,7 +141,7 @@ export default function EncodingSelect(props) {
|
|||||||
|
|
||||||
for (let c of encoderRegistry.List()) {
|
for (let c of encoderRegistry.List()) {
|
||||||
// Does ffmpeg support the coder?
|
// Does ffmpeg support the coder?
|
||||||
if (!props.availableEncoders.includes(c.coder)) {
|
if (!availableEncoders.includes(c.coder)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,14 +155,14 @@ export default function EncodingSelect(props) {
|
|||||||
encoderList.push(
|
encoderList.push(
|
||||||
<MenuItem value={c.coder} key={c.coder}>
|
<MenuItem value={c.coder} key={c.coder}>
|
||||||
{c.name}
|
{c.name}
|
||||||
</MenuItem>
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
encoderList.push(
|
encoderList.push(
|
||||||
<MenuItem value={c.coder} key={c.coder}>
|
<MenuItem value={c.coder} key={c.coder}>
|
||||||
{c.name}
|
{c.name}
|
||||||
</MenuItem>
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -173,22 +184,24 @@ export default function EncodingSelect(props) {
|
|||||||
|
|
||||||
if (coder.coder !== 'copy' && coder.coder !== 'none') {
|
if (coder.coder !== 'copy' && coder.coder !== 'none') {
|
||||||
let c = decoderRegistry.Get(profile.decoder.coder);
|
let c = decoderRegistry.Get(profile.decoder.coder);
|
||||||
if (c !== null && props.availableDecoders.includes(c.coder)) {
|
if (c !== null && availableDecoders.includes(c.coder)) {
|
||||||
const Settings = c.component;
|
const Settings = c.component;
|
||||||
|
|
||||||
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} onChange={handleDecoderSettingsChange} />;
|
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} skills={props.skills} onChange={handleDecoderSettingsChange} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List all decoders for the codec of the stream
|
// List all decoders for the codec of the stream
|
||||||
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, props.availableDecoders, 'any')) {
|
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, availableDecoders, 'any')) {
|
||||||
decoderList.push(
|
decoderList.push(
|
||||||
<MenuItem value={c.coder} key={c.coder}>
|
<MenuItem value={c.coder} key={c.coder}>
|
||||||
{c.name}
|
{c.name}
|
||||||
</MenuItem>
|
</MenuItem>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: in case there's no decoder for a codec it should be mentioned.
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
@ -237,7 +250,6 @@ EncodingSelect.defaultProps = {
|
|||||||
streams: [],
|
streams: [],
|
||||||
profile: {},
|
profile: {},
|
||||||
codecs: [],
|
codecs: [],
|
||||||
availableEncoders: [],
|
skills: {},
|
||||||
availableDecoders: [],
|
onChange: function (encoder, decoder, automatic) {},
|
||||||
onChange: function (encoder, decoder) {},
|
|
||||||
};
|
};
|
||||||
|
|||||||
28
src/misc/Filesize.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
// Adapted from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
|
||||||
|
export default function Filesize(props) {
|
||||||
|
let bytes = props.bytes;
|
||||||
|
const thresh = props.si ? 1000 : 1024;
|
||||||
|
|
||||||
|
if (Math.abs(bytes) < thresh) {
|
||||||
|
return bytes + ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = props.si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
let u = -1;
|
||||||
|
const r = 10 ** props.digits;
|
||||||
|
|
||||||
|
do {
|
||||||
|
bytes /= thresh;
|
||||||
|
++u;
|
||||||
|
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
|
||||||
|
|
||||||
|
return <React.Fragment>{bytes.toFixed(props.digits) + ' ' + units[u]}</React.Fragment>;
|
||||||
|
}
|
||||||
|
|
||||||
|
Filesize.defaultProps = {
|
||||||
|
bytes: 0,
|
||||||
|
si: false,
|
||||||
|
digits: 1,
|
||||||
|
};
|
||||||
134
src/misc/FilterSelect.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { Trans } from '@lingui/macro';
|
||||||
|
import Grid from '@mui/material/Grid';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
|
||||||
|
// Import all filters (audio/video)
|
||||||
|
import * as Filters from './filters';
|
||||||
|
|
||||||
|
// Import all encoders (audio/video)
|
||||||
|
import * as Encoders from './coders/Encoders';
|
||||||
|
|
||||||
|
export default function FilterSelect(props) {
|
||||||
|
const profile = props.profile;
|
||||||
|
|
||||||
|
// handleFilterChange
|
||||||
|
// what: Filter name
|
||||||
|
// settings (component settings): {Key: Value}
|
||||||
|
// mapping (FFmpeg -af/-vf args): ['String', ...]
|
||||||
|
const handleFilterSettingsChange = (what) => (settings, graph, automatic) => {
|
||||||
|
const filter = profile.filter;
|
||||||
|
|
||||||
|
// Store mapping/settings per component
|
||||||
|
filter.settings[what] = {
|
||||||
|
settings: settings,
|
||||||
|
graph: graph,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get the order of the filters
|
||||||
|
let filterOrder = [];
|
||||||
|
if (props.type === 'video') {
|
||||||
|
filterOrder = Filters.Video.Filters();
|
||||||
|
} else {
|
||||||
|
filterOrder = Filters.Audio.Filters();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the filter graph in the order as the filters are registered
|
||||||
|
const graphs = [];
|
||||||
|
for (let f of filterOrder) {
|
||||||
|
if (!(f in filter.settings)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter.settings[f].graph.length !== 0) {
|
||||||
|
graphs.push(filter.settings[f].graph);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.graph = graphs.join(',');
|
||||||
|
|
||||||
|
props.onChange(filter, automatic);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set filterRegistry by type
|
||||||
|
let filterRegistry = null;
|
||||||
|
if (props.type === 'video') {
|
||||||
|
filterRegistry = Filters.Video;
|
||||||
|
} else if (props.type === 'audio') {
|
||||||
|
filterRegistry = Filters.Audio;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks the state of hwaccel (gpu encoding)
|
||||||
|
let encoderRegistry = null;
|
||||||
|
let hwaccel = false;
|
||||||
|
if (props.type === 'video') {
|
||||||
|
encoderRegistry = Encoders.Video;
|
||||||
|
for (let encoder of encoderRegistry.List()) {
|
||||||
|
if (encoder.codec === props.profile.encoder.coder && encoder.hwaccel) {
|
||||||
|
hwaccel = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates filter components
|
||||||
|
let filterSettings = [];
|
||||||
|
if (!hwaccel) {
|
||||||
|
for (let c of filterRegistry.List()) {
|
||||||
|
// Checks FFmpeg skills (filter is available)
|
||||||
|
if (props.availableFilters.includes(c.filter)) {
|
||||||
|
const Settings = c.component;
|
||||||
|
|
||||||
|
if (!(c.filter in profile.filter.settings)) {
|
||||||
|
profile.filter.settings[c.filter] = c.defaults();
|
||||||
|
} else {
|
||||||
|
profile.filter.settings[c.filter] = {
|
||||||
|
...c.defaults(),
|
||||||
|
...profile.filter.settings[c.filter],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
filterSettings.push(
|
||||||
|
<Settings key={c.filter} settings={profile.filter.settings[c.filter].settings} onChange={handleFilterSettingsChange(c.filter)} />,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No suitable filter found
|
||||||
|
if (filterSettings === null && !hwaccel) {
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography>
|
||||||
|
<Trans>No suitable filter found.</Trans>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
|
||||||
|
// hwaccel requires further settings
|
||||||
|
} else if (hwaccel) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={2}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Typography>
|
||||||
|
<Trans>Select your filter settings (optional):</Trans>
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
{filterSettings}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FilterSelect.defaultProps = {
|
||||||
|
type: '',
|
||||||
|
profile: {},
|
||||||
|
availableFilters: [],
|
||||||
|
onChange: function (filter, automatic) {},
|
||||||
|
};
|
||||||
@ -42,14 +42,21 @@ export default function LanguageSelect(props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Select className={classes.root} variant="standard" displayEmpty value={i18n.locale} onChange={handleChange}>
|
<Select className={classes.root} variant="standard" displayEmpty value={i18n.locale} onChange={handleChange}>
|
||||||
<MenuItem value="en">English </MenuItem>
|
<MenuItem value="en">English</MenuItem>
|
||||||
<MenuItem value="de">Deutsch </MenuItem>
|
<MenuItem value="da">Dansk</MenuItem>
|
||||||
<MenuItem value="es">Español </MenuItem>
|
<MenuItem value="de">Deutsch</MenuItem>
|
||||||
<MenuItem value="fr">Français </MenuItem>
|
<MenuItem value="el">Ελληνικά</MenuItem>
|
||||||
<MenuItem value="it">Italiano </MenuItem>
|
<MenuItem value="es">Español</MenuItem>
|
||||||
|
<MenuItem value="fr">Français</MenuItem>
|
||||||
|
<MenuItem value="it">Italiano</MenuItem>
|
||||||
|
<MenuItem value="ko">한국어</MenuItem>
|
||||||
<MenuItem value="pl">Polski</MenuItem>
|
<MenuItem value="pl">Polski</MenuItem>
|
||||||
<MenuItem value="pt">Português </MenuItem>
|
<MenuItem value="pt-br">Português</MenuItem>
|
||||||
<MenuItem value="ru">Русский </MenuItem>
|
<MenuItem value="ru">Русский</MenuItem>
|
||||||
|
<MenuItem value="sl">Slovenščina</MenuItem>
|
||||||
|
<MenuItem value="tr">Türkçe</MenuItem>
|
||||||
|
<MenuItem value="uk">Українська мова</MenuItem>
|
||||||
|
<MenuItem value="zh-hans">中文(简体)</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 6.8 KiB |
@ -15,16 +15,9 @@ const MenuProps = {
|
|||||||
|
|
||||||
export default function Component(props) {
|
export default function Component(props) {
|
||||||
return (
|
return (
|
||||||
<FormControl variant="outlined" fullWidth>
|
<FormControl variant={props.variant} disabled={props.disabled} fullWidth>
|
||||||
<InputLabel>{props.label}</InputLabel>
|
<InputLabel>{props.label}</InputLabel>
|
||||||
<Select
|
<Select multiple value={props.value} onChange={props.onChange} input={<OutlinedInput />} renderValue={props.renderValue} MenuProps={MenuProps}>
|
||||||
multiple
|
|
||||||
value={props.value}
|
|
||||||
onChange={props.onChange}
|
|
||||||
input={<OutlinedInput />}
|
|
||||||
renderValue={(selected) => selected.join(', ')}
|
|
||||||
MenuProps={MenuProps}
|
|
||||||
>
|
|
||||||
{props.children}
|
{props.children}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -32,7 +25,10 @@ export default function Component(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.defaultProps = {
|
Component.defaultProps = {
|
||||||
|
variant: 'outlined',
|
||||||
label: '',
|
label: '',
|
||||||
value: [],
|
value: [],
|
||||||
|
disabled: false,
|
||||||
|
renderValue: (selected) => selected.join(', '),
|
||||||
onChange: function (event) {},
|
onChange: function (event) {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -53,6 +53,8 @@ export default function Password(props) {
|
|||||||
endAdornment={adornment}
|
endAdornment={adornment}
|
||||||
label={props.label}
|
label={props.label}
|
||||||
autoComplete={props.autoComplete}
|
autoComplete={props.autoComplete}
|
||||||
|
inputProps={props.inputProps}
|
||||||
|
error={props.error}
|
||||||
/>
|
/>
|
||||||
{props.helperText && <FormHelperText>{props.helperText}</FormHelperText>}
|
{props.helperText && <FormHelperText>{props.helperText}</FormHelperText>}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
@ -67,6 +69,8 @@ Password.defaultProps = {
|
|||||||
autoComplete: 'current-password',
|
autoComplete: 'current-password',
|
||||||
env: false,
|
env: false,
|
||||||
show: false,
|
show: false,
|
||||||
helperText: null,
|
helperText: false,
|
||||||
|
inputProps: {},
|
||||||
|
error: false,
|
||||||
onChange: function (value) {},
|
onChange: function (value) {},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,101 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Plugins } from '@clappr/plugins';
|
|
||||||
import Clappr from '@clappr/core';
|
|
||||||
import Grid from '@mui/material/Grid';
|
|
||||||
import HLS from '@clappr/hlsjs-playback';
|
|
||||||
//import ClapprStats from '@clappr/stats-plugin';
|
|
||||||
|
|
||||||
/*
|
|
||||||
import Clappr from 'clappr';
|
|
||||||
import ClapprStats from 'clappr-stats';
|
|
||||||
*/
|
|
||||||
|
|
||||||
//import ClapprNerdStats from 'clappr-nerd-stats';
|
|
||||||
|
|
||||||
Clappr.Loader.registerPlayback(HLS);
|
|
||||||
|
|
||||||
for (let plugin of Object.values(Plugins)) {
|
|
||||||
Clappr.Loader.registerPlugin(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
class Player extends React.Component {
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
this.player = new Clappr.Player({});
|
|
||||||
|
|
||||||
this.playerRef = React.createRef();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.player.attachTo(this.playerRef.current);
|
|
||||||
this.setConfig(this.props.config);
|
|
||||||
this.setSource(this.props.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
this.player.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, _) {
|
|
||||||
if (nextProps.source !== this.props.source) {
|
|
||||||
this.setSource(nextProps.source);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSource(source) {
|
|
||||||
this.player.load(source);
|
|
||||||
}
|
|
||||||
|
|
||||||
setConfig(config) {
|
|
||||||
delete config.source;
|
|
||||||
delete config.sources;
|
|
||||||
delete config.parent;
|
|
||||||
delete config.parentid;
|
|
||||||
|
|
||||||
config = {
|
|
||||||
plugins: [],
|
|
||||||
...config,
|
|
||||||
};
|
|
||||||
|
|
||||||
const plugins = config.plugins;
|
|
||||||
config.plugins = [];
|
|
||||||
|
|
||||||
for (let p of plugins) {
|
|
||||||
switch (p) {
|
|
||||||
/*
|
|
||||||
case 'ClapprStats':
|
|
||||||
config.plugins.push(ClapprStats);
|
|
||||||
break;
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
case 'ClapprNerdStats':
|
|
||||||
config.plugins.push(ClapprNerdStats);
|
|
||||||
break;
|
|
||||||
*/
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.player.configure(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<Grid
|
|
||||||
container
|
|
||||||
direction="column"
|
|
||||||
justifyContent="center"
|
|
||||||
alignItems="center"
|
|
||||||
spacing={1}
|
|
||||||
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
|
|
||||||
ref={this.playerRef}
|
|
||||||
></Grid>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Player;
|
|
||||||
@ -1,10 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import Clappr from './clappr';
|
|
||||||
import VideoJS from './videojs';
|
import VideoJS from './videojs';
|
||||||
|
|
||||||
export default function Player(props) {
|
export default function Player(props) {
|
||||||
const type = props.type ? props.type : 'clappr';
|
const type = props.type ? props.type : 'videojs-internal';
|
||||||
|
|
||||||
if (type === 'videojs-internal' || type === 'videojs-public') {
|
if (type === 'videojs-internal' || type === 'videojs-public') {
|
||||||
const config = {
|
const config = {
|
||||||
@ -15,6 +14,9 @@ export default function Player(props) {
|
|||||||
liveui: true,
|
liveui: true,
|
||||||
responsive: true,
|
responsive: true,
|
||||||
fluid: true,
|
fluid: true,
|
||||||
|
plugins: {
|
||||||
|
reloadSourceOnError: {},
|
||||||
|
},
|
||||||
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
|
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -43,17 +45,19 @@ export default function Player(props) {
|
|||||||
overlay = imgTag.outerHTML;
|
overlay = imgTag.outerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.overlay({
|
if (player.overlay) {
|
||||||
align: props.logo.position,
|
player.overlay({
|
||||||
overlays: [
|
align: props.logo.position,
|
||||||
{
|
overlays: [
|
||||||
showBackground: false,
|
{
|
||||||
content: overlay,
|
showBackground: false,
|
||||||
start: 'playing',
|
content: overlay,
|
||||||
end: 'pause',
|
start: 'playing',
|
||||||
},
|
end: 'pause',
|
||||||
],
|
},
|
||||||
});
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props.autoplay === true) {
|
if (props.autoplay === true) {
|
||||||
@ -76,57 +80,6 @@ export default function Player(props) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
const config = {
|
|
||||||
poster: props.poster,
|
|
||||||
autoPlay: props.autoplay,
|
|
||||||
mute: props.mute,
|
|
||||||
disableCanAutoPlay: true,
|
|
||||||
playback: {
|
|
||||||
playInline: true,
|
|
||||||
hlsjsConfig: {
|
|
||||||
enableWorker: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
chromeless: !props.controls,
|
|
||||||
height: '100%',
|
|
||||||
width: '100%',
|
|
||||||
mediacontrol: {
|
|
||||||
seekbar: props.colors.seekbar,
|
|
||||||
buttons: props.colors.buttons,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (props.logo.image.length !== 0) {
|
|
||||||
config.watermark = props.logo.image + '?' + Math.random();
|
|
||||||
config.position = props.logo.position;
|
|
||||||
|
|
||||||
if (props.logo.link.length !== 0) {
|
|
||||||
config.watermarkLink = props.logo.link;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.ga.account.length !== 0) {
|
|
||||||
config.gaAccount = props.ga.account;
|
|
||||||
|
|
||||||
if (props.ga.name.length !== 0) {
|
|
||||||
config.gaTrackerName = props.ga.name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props.statistics === true) {
|
|
||||||
config.plugins.push('ClapprStats', 'clapprNerdStats');
|
|
||||||
config.clapprStats = {
|
|
||||||
runEach: 1000,
|
|
||||||
onReport: (metrics) => {},
|
|
||||||
};
|
|
||||||
config.clapprNerdStats = {
|
|
||||||
shortcut: ['command+shift+s', 'ctrl+shift+s'],
|
|
||||||
iconPosition: 'top-right',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return <Clappr source={props.source} config={config} />;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -89,7 +89,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
||||||
top: -0.4em;
|
top: 0em;
|
||||||
right: -0.5em;
|
right: -0.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
99
src/misc/Player/video-js-skin-internal.min.css
vendored
@ -1,98 +1 @@
|
|||||||
.vjs-internal {
|
.vjs-internal{--video-js--primary:#eaea05}.vjs-internal .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;color:rgba(255,255,255,.65)}.vjs-internal.vjs-big-play-button:focus,.vjs-internal:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-internal .vjs-control-bar{top:calc(50% - 45px);height:100px;width:50px;background-color:#4d4d4d;border-top-right-radius:4px;border-bottom-right-radius:4px}.vjs-internal .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-internal .vjs-play-progress:before{display:none}.vjs-internal .vjs-progress-control{display:none}.vjs-internal .vjs-paused,.vjs-internal .vjs-playing{width:50px;height:50px;border-radius:4px;background-color:#4d4d4d}.vjs-internal .vjs-volume-panel{width:50px;height:50px;border-radius:4px;padding-left:4px;background-color:#4d4d4d;margin:50px 0 0 -50px}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,right 1s 1s,top 1s 1s}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.vjs-internal .vjs-volume-panel .vjs-volume-control:active,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-mute-control~.vjs-volume-control,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,.vjs-internal .vjs-volume-panel:active .vjs-volume-control,.vjs-internal .vjs-volume-panel:focus .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,right 0s,top 0s}.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before{top:0;right:-.5em}.vjs-internal .vjs-remaining-time{display:none}.vjs-internal .vjs-live-display{display:none}.vjs-internal .vjs-picture-in-picture-control{display:none}.vjs-internal .vjs-fullscreen-control{display:none}.vjs-internal .vjs-seek-to-live-control{display:none}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-internal .vjs-custom-control-spacer{display:block;width:100%}
|
||||||
--video-js--primary: #eaea05;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-big-play-button {
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
background: 0 0;
|
|
||||||
line-height: 180px;
|
|
||||||
font-size: 180px;
|
|
||||||
border: none;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin-top: -90px;
|
|
||||||
color: rgba(255, 255, 255, 0.65);
|
|
||||||
}
|
|
||||||
.vjs-internal.vjs-big-play-button:focus,
|
|
||||||
.vjs-internal:hover .vjs-big-play-button {
|
|
||||||
background-color: transparent;
|
|
||||||
color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-control-bar {
|
|
||||||
top: calc(50% - 45px);
|
|
||||||
height: 100px;
|
|
||||||
width: 50px;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-button > .vjs-icon-placeholder:before {
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-play-progress:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-progress-control {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-paused,
|
|
||||||
.vjs-internal .vjs-playing {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-volume-panel {
|
|
||||||
width: 50px;
|
|
||||||
height: 50px;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding-left: 4px;
|
|
||||||
background-color: #4d4d4d;
|
|
||||||
margin: 50px 0 0 -50px;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
|
||||||
padding-top: 1em;
|
|
||||||
transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, right 1s 1s,
|
|
||||||
top 1s 1s;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,
|
|
||||||
.vjs-internal .vjs-volume-panel .vjs-volume-control:active,
|
|
||||||
.vjs-internal
|
|
||||||
.vjs-volume-panel.vjs-hover
|
|
||||||
.vjs-mute-control
|
|
||||||
~ .vjs-volume-control,
|
|
||||||
.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,
|
|
||||||
.vjs-internal .vjs-volume-panel:active .vjs-volume-control,
|
|
||||||
.vjs-internal .vjs-volume-panel:focus .vjs-volume-control {
|
|
||||||
visibility: visible;
|
|
||||||
opacity: 1;
|
|
||||||
position: relative;
|
|
||||||
transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, right 0s,
|
|
||||||
top 0s;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
|
|
||||||
top: -0.4em;
|
|
||||||
right: -0.5em;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-remaining-time {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-live-display {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-picture-in-picture-control {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-fullscreen-control {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-seek-to-live-control {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-subs-caps-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-custom-control-spacer {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -1,117 +1,117 @@
|
|||||||
.vjs-public {
|
.vjs-public {
|
||||||
--video-js--primary: #eaea05;
|
--video-js--primary: #EAEA05;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* play btn */
|
/* play btn */
|
||||||
|
|
||||||
.vjs-public .vjs-big-play-button {
|
.vjs-public .vjs-big-play-button {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
background: none;
|
background: none;
|
||||||
line-height: 180px;
|
line-height: 180px;
|
||||||
font-size: 180px;
|
font-size: 180px;
|
||||||
border: none;
|
border: none;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
margin-top: -90px;
|
margin-top: -90px;
|
||||||
margin-left: -90px;
|
margin-left: -90px;
|
||||||
color: rgba(255, 255, 255, 0.65);
|
color: rgba(255,255,255,.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public:hover .vjs-big-play-button,
|
.vjs-public:hover .vjs-big-play-button,
|
||||||
.vjs-public.vjs-big-play-button:focus {
|
.vjs-public.vjs-big-play-button:focus {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: rgba(255, 255, 255, 1);
|
color: rgba(255,255,255,1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* controlbar */
|
/* controlbar */
|
||||||
|
|
||||||
.vjs-public .vjs-control-bar {
|
.vjs-public .vjs-control-bar {
|
||||||
height: 70px;
|
height: 70px;
|
||||||
padding-top: 20px;
|
padding-top: 20px;
|
||||||
background: none;
|
background: none;
|
||||||
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
background-image: linear-gradient(0deg, rgba(0,0,0,.85), transparent)
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-time-tooltip {
|
.vjs-public .vjs-time-tooltip {
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
|
.vjs-public .vjs-button>.vjs-icon-placeholder:before {
|
||||||
line-height: 50px;
|
line-height: 50px
|
||||||
}
|
}
|
||||||
|
|
||||||
/* progressbar */
|
/* progressbar */
|
||||||
|
|
||||||
.vjs-public .vjs-play-progress:before {
|
.vjs-public .vjs-play-progress:before {
|
||||||
display: none;
|
display: none
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-progress-control {
|
.vjs-public .vjs-progress-control {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 15px;
|
left: 15px;
|
||||||
width: calc(100% - 30px);
|
width: calc(100% - 30px);
|
||||||
height: 20px;
|
height: 20px
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 20px;
|
top: 20px;
|
||||||
right: 0;
|
right: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-play-progress {
|
.vjs-public .vjs-play-progress {
|
||||||
background-color: var(--video-js--primary);
|
background-color: var(--video-js--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-slider {
|
.vjs-public .vjs-slider {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255,255,255,.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-load-progress {
|
.vjs-public .vjs-load-progress {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255,255,255,.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-load-progress div {
|
.vjs-public .vjs-load-progress div {
|
||||||
background: rgba(255, 255, 255, 0.25);
|
background: rgba(255,255,255,.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-remaining-time {
|
.vjs-public .vjs-remaining-time {
|
||||||
order: 0;
|
order: 0;
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
flex: 3;
|
flex: 3;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-live-control {
|
.vjs-public .vjs-live-control {
|
||||||
line-height: 50px;
|
line-height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* volume-panel */
|
/* volume-panel */
|
||||||
|
|
||||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||||
padding-top: 1em;
|
padding-top: .95em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-control .vjs-volume-panel {
|
.vjs-public .vjs-control .vjs-volume-panel {
|
||||||
width: 4.5em;
|
width: 4.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* live display */
|
/* live display */
|
||||||
|
|
||||||
.vjs-public .vjs-live-display {
|
.vjs-public .vjs-live-display {
|
||||||
margin-left: 1.8em;
|
margin-left: 1.8em;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* disable caps */
|
/* disable caps */
|
||||||
|
|
||||||
.vjs-internal .vjs-subs-caps-button {
|
.vjs-public .vjs-subs-caps-button {
|
||||||
display: none;
|
display: none!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* spacer */
|
/* spacer */
|
||||||
@ -123,47 +123,52 @@
|
|||||||
|
|
||||||
/* overlay */
|
/* overlay */
|
||||||
|
|
||||||
.vjs-public .vjs-overlay > a > img {
|
.vjs-public .vjs-overlay-no-background {
|
||||||
width: 100%;
|
max-width: 25%!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-no-background {
|
.vjs-public .vjs-overlay-no-background > img, .vjs-public .vjs-overlay-no-background > a > img {
|
||||||
max-width: 28% !important;
|
max-width: 100%!important;
|
||||||
max-height: 28% !important;
|
height: auto!important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-left {
|
.vjs-public .vjs-overlay-top-left {
|
||||||
top: 20px !important;
|
top: 15px!important;
|
||||||
left: 30px !important;
|
left: 20px!important;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-top-right {
|
.vjs-public .vjs-overlay-top-right {
|
||||||
top: 20px !important;
|
top: 15px!important;
|
||||||
right: 30px !important;
|
right: 20px!important;
|
||||||
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-left {
|
.vjs-public .vjs-overlay-bottom-left {
|
||||||
bottom: 20px !important;
|
bottom: 15px!important;
|
||||||
left: 30px !important;
|
left: 20px!important;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-overlay-bottom-right {
|
.vjs-public .vjs-overlay-bottom-right {
|
||||||
bottom: 20px !important;
|
bottom: 15px!important;
|
||||||
right: 30px !important;
|
right: 20px!important;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* context menu */
|
/* context menu */
|
||||||
|
|
||||||
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
||||||
background: rgba(0, 0, 0, 0.8);
|
background: rgba(0,0,0,.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-license-top-level-header {
|
.vjs-public .vjs-license-top-level-header {
|
||||||
background: unset !important;
|
background: unset!important;
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
border-bottom: 1px solid rgba(255,255,255,.25);
|
||||||
min-width: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vjs-public .vjs-lock-open {
|
.vjs-public .vjs-lock-open {
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
}
|
}
|
||||||
|
|||||||
124
src/misc/Player/video-js-skin-public.min.css
vendored
@ -1,123 +1 @@
|
|||||||
.vjs-public {
|
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:none;line-height:180px;font-size:180px;border:0;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public:hover .vjs-big-play-button,.vjs-public.vjs-big-play-button:focus{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:none;background-image:linear-gradient(0,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:.95em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-public .vjs-subs-caps-button{display:none!important}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay-no-background{max-width:25%!important}.vjs-public .vjs-overlay-no-background>img,.vjs-public .vjs-overlay-no-background>a>img{max-width:100%!important;height:auto!important}.vjs-public .vjs-overlay-top-left{top:15px!important;left:20px!important;text-align:right}.vjs-public .vjs-overlay-top-right{top:15px!important;right:20px!important;text-align:right}.vjs-public .vjs-overlay-bottom-left{bottom:15px!important;left:20px!important;text-align:left}.vjs-public .vjs-overlay-bottom-right{bottom:15px!important;right:20px!important;text-align:left}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}
|
||||||
--video-js--primary: #eaea05;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-big-play-button {
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
background: 0 0;
|
|
||||||
line-height: 180px;
|
|
||||||
font-size: 180px;
|
|
||||||
border: none;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
margin-top: -90px;
|
|
||||||
margin-left: -90px;
|
|
||||||
color: rgba(255, 255, 255, 0.65);
|
|
||||||
}
|
|
||||||
.vjs-public.vjs-big-play-button:focus,
|
|
||||||
.vjs-public:hover .vjs-big-play-button {
|
|
||||||
background-color: transparent;
|
|
||||||
color: rgba(255, 255, 255, 1);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-control-bar {
|
|
||||||
height: 70px;
|
|
||||||
padding-top: 20px;
|
|
||||||
background: 0 0;
|
|
||||||
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-time-tooltip {
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-play-progress:before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-progress-control {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
left: 15px;
|
|
||||||
width: calc(100% - 30px);
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
|
||||||
position: absolute;
|
|
||||||
top: 20px;
|
|
||||||
right: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-play-progress {
|
|
||||||
background-color: var(--video-js--primary);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-slider {
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-load-progress {
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-load-progress div {
|
|
||||||
background: rgba(255, 255, 255, 0.25);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-remaining-time {
|
|
||||||
order: 0;
|
|
||||||
line-height: 50px;
|
|
||||||
flex: 3;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-live-control {
|
|
||||||
line-height: 50px;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
|
||||||
padding-top: 1em;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-control .vjs-volume-panel {
|
|
||||||
width: 4.5em;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-live-display {
|
|
||||||
margin-left: 1.8em;
|
|
||||||
}
|
|
||||||
.vjs-internal .vjs-subs-caps-button {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-custom-control-spacer {
|
|
||||||
display: block;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay > a > img {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay-no-background {
|
|
||||||
max-width: 28% !important;
|
|
||||||
max-height: 28% !important;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay-top-left {
|
|
||||||
top: 20px !important;
|
|
||||||
left: 30px !important;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay-top-right {
|
|
||||||
top: 20px !important;
|
|
||||||
right: 30px !important;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay-bottom-left {
|
|
||||||
bottom: 20px !important;
|
|
||||||
left: 30px !important;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-overlay-bottom-right {
|
|
||||||
bottom: 20px !important;
|
|
||||||
right: 30px !important;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
|
||||||
background: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-license-top-level-header {
|
|
||||||
background: unset !important;
|
|
||||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
|
||||||
min-width: 100px;
|
|
||||||
}
|
|
||||||
.vjs-public .vjs-lock-open {
|
|
||||||
z-index: 1000;
|
|
||||||
}
|
|
||||||
@ -31,6 +31,7 @@ export default function VideoJS(props) {
|
|||||||
player.addClass('vjs-internal');
|
player.addClass('vjs-internal');
|
||||||
}
|
}
|
||||||
player.addClass('video-js');
|
player.addClass('video-js');
|
||||||
|
player.addClass('vjs-16-9');
|
||||||
} else {
|
} else {
|
||||||
// you can update player here [update player through props]
|
// you can update player here [update player through props]
|
||||||
// const player = playerRef.current;
|
// const player = playerRef.current;
|
||||||
@ -57,7 +58,7 @@ export default function VideoJS(props) {
|
|||||||
direction="column"
|
direction="column"
|
||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
spacing={1}
|
spacing={2}
|
||||||
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
|
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
|
||||||
>
|
>
|
||||||
<div data-vjs-player>
|
<div data-vjs-player>
|
||||||
|
|||||||