Compare commits
271 Commits
snyk-fix-e
...
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 | ||
|
|
e587f2076b |
@ -10,3 +10,4 @@ node_modules/
|
||||
.github
|
||||
.github_build
|
||||
.build
|
||||
NONPUBLIC/
|
||||
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
||||
src/locales/*/messages.js
|
||||
130
.github/workflows/build_restreamer-ui.yaml
vendored
130
.github/workflows/build_restreamer-ui.yaml
vendored
@ -1,62 +1,88 @@
|
||||
name: 'Build restreamer-ui'
|
||||
name: 'Build main restreamer-ui'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore:
|
||||
- '**'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: [self-hosted]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.latestversion.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- uses: cardinalby/export-env-action@v1
|
||||
with:
|
||||
envFile: '.github_build/Build.restreamer-ui.env'
|
||||
export: 'true'
|
||||
expandWithJobEnv: 'true'
|
||||
expand: 'true'
|
||||
- name: Get latest version from package.json
|
||||
id: latestversion
|
||||
run: |
|
||||
echo "version=$(cat ./package.json | jq '.version' | sed 's/\"//g')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '21'
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
- name: Build React App
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
||||
env:
|
||||
PUBLIC_URL: './'
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Upload React build as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: restreamerui-main-build
|
||||
path: build/
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
build-docker:
|
||||
needs: build-frontend
|
||||
runs-on: [self-hosted]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Build Multi-Arch
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
PUBLIC_URL=./
|
||||
NODE_IMAGE=${{ env.NODE_IMAGE }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
datarhei/restreamer-ui:${{ env.RELEASE }}
|
||||
datarhei/restreamer-ui:latest
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
- name: Download React build artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: restreamerui-main-build
|
||||
path: build
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
datarhei/restreamer-ui
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
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
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 }}
|
||||
73
.github/workflows/build_restreamer_dev.yaml
vendored
73
.github/workflows/build_restreamer_dev.yaml
vendored
@ -1,73 +0,0 @@
|
||||
name: 'Build datarhei/restreamer-ui:dev'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
push:
|
||||
branches:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
runs-on: [self-hosted]
|
||||
strategy:
|
||||
matrix:
|
||||
branch:
|
||||
- dev
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
|
||||
- uses: actions-ecosystem/action-get-latest-tag@v1
|
||||
id: get-latest-tag
|
||||
with:
|
||||
semver_only: true
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
datarhei/restreamer-ui
|
||||
tags: |
|
||||
type=raw,value=dev,enable=${{ matrix.branch == '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: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
- name: Login to DockerHub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Build Multi-Arch
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
build-args: |
|
||||
PUBLIC_URL=/ui
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
@ -1,3 +0,0 @@
|
||||
# RESTREAMER UI
|
||||
RELEASE=1.4.0
|
||||
NODE_IMAGE=node:18.9.0-alpine3.15
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/NONPUBLIC
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
@ -12,6 +13,7 @@
|
||||
/build
|
||||
|
||||
# misc
|
||||
NONPUBLIC
|
||||
.DS_Store
|
||||
.VSCodeCounter
|
||||
.env.local
|
||||
|
||||
13
.linguirc
13
.linguirc
@ -14,12 +14,19 @@
|
||||
"sourceLocale": "en",
|
||||
"locales": [
|
||||
"en",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt",
|
||||
"es",
|
||||
"ru"
|
||||
"pt-br",
|
||||
"ru",
|
||||
"sl",
|
||||
"tr",
|
||||
"uk",
|
||||
"zh-hans"
|
||||
]
|
||||
}
|
||||
149
CHANGELOG.md
149
CHANGELOG.md
@ -1,6 +1,145 @@
|
||||
# Restreamer-UI
|
||||
|
||||
### v1.3.0 > v1.4.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
|
||||
|
||||
@ -8,11 +147,11 @@ Dependency:
|
||||
|
||||
- datarhei Core v16.10.1+
|
||||
|
||||
### v1.2.0 > v1.3.0
|
||||
## 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)
|
||||
- Add dlive & Trovo publication services
|
||||
- Mod optimized DVR on DiskFS
|
||||
- Mod updates packages
|
||||
- Fix SRT bitstream on tee
|
||||
@ -25,7 +164,7 @@ Dependency:
|
||||
|
||||
- datarhei Core v16.10.0+
|
||||
|
||||
#### v1.1.0 > v1.2.0
|
||||
## v1.1.0 > v1.2.0
|
||||
|
||||
- Add allow writing HLS to disk
|
||||
- Add audio pan filter
|
||||
@ -56,7 +195,7 @@ Dependency:
|
||||
|
||||
- datarhei Core v16.9.0+
|
||||
|
||||
#### v1.0.0 > v1.1.0
|
||||
## v1.0.0 > v1.1.0
|
||||
|
||||
- Add compatibility list for encoders
|
||||
- Add "HLS cleanup" as an optional function ([Philipp Trenz](https://github.com/philipptrenz))
|
||||
|
||||
22
Dockerfile
22
Dockerfile
@ -1,25 +1,17 @@
|
||||
ARG NODE_IMAGE=node:18.6.0-alpine3.15
|
||||
ARG CADDY_IMAGE=caddy:2.5.2-alpine
|
||||
ARG NODE_IMAGE=node:21-alpine3.20
|
||||
ARG CADDY_IMAGE=caddy:2.8.4-alpine
|
||||
|
||||
FROM $NODE_IMAGE as builder
|
||||
FROM $NODE_IMAGE AS builder
|
||||
|
||||
ARG NODE_SPACE_SIZE=10240
|
||||
ENV NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=$NODE_SPACE_SIZE"
|
||||
|
||||
ENV PUBLIC_URL "./"
|
||||
ENV PUBLIC_URL="./"
|
||||
|
||||
COPY . /ui
|
||||
|
||||
WORKDIR /ui
|
||||
|
||||
RUN cd /ui && \
|
||||
npm config set fetch-retries 10 && \
|
||||
npm config set fetch-retry-mintimeout 100000 && \
|
||||
npm config set fetch-retry-maxtimeout 600000 && \
|
||||
npm config set cache-min 3600 && \
|
||||
npm config ls -l && \
|
||||
npm install && \
|
||||
npm run build
|
||||
yarn install && \
|
||||
yarn build
|
||||
|
||||
FROM $CADDY_IMAGE
|
||||
|
||||
@ -30,4 +22,4 @@ WORKDIR /ui
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "caddy", "run", "-config", "/ui/Caddyfile" ]
|
||||
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]
|
||||
|
||||
13
Dockerfile.workflow
Normal file
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" ]
|
||||
18
README.md
18
README.md
@ -26,23 +26,5 @@ $ npm run i18n-extract:clean
|
||||
$ npm run i18n-compile
|
||||
```
|
||||
|
||||
### Known outdated dependencies
|
||||
Requires MUI 5.2+ & React 18 compatibility. Clappr-Player upgrade (or removal).
|
||||
|
||||
```sh
|
||||
@mui/material 5.1.1 → 5.9.0
|
||||
@mui/styles ^5.1.1 → ^5.9.0
|
||||
@testing-library/dom ^8.13.0 → ^8.16.0
|
||||
@testing-library/jest-dom ^4.2.4 → ^5.16.4
|
||||
@testing-library/react ^12.1.5 → ^13.3.0
|
||||
@testing-library/user-event ^13.5.0 → ^14.2.5
|
||||
eslint ^7.32.0 → ^8.19.0
|
||||
hls.js ^0.14.17 → ^1.1.5
|
||||
react ^17.0.2 → ^18.2.0
|
||||
react-dom ^17.0.2 → ^18.2.0
|
||||
react-scripts ^4.0.3 → ^5.0.1
|
||||
typescript ^3.9.7 → ^4.7.4
|
||||
```
|
||||
|
||||
## License
|
||||
See the [LICENSE](./LICENSE) file for licensing information.
|
||||
|
||||
103
package.json
103
package.json
@ -1,57 +1,61 @@
|
||||
{
|
||||
"name": "restreamer-ui",
|
||||
"version": "1.4.0",
|
||||
"bundle": "restreamer-v2.3.0",
|
||||
"version": "1.14.0",
|
||||
"bundle": "restreamer-v2.12.0",
|
||||
"private": false,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^1.22.3",
|
||||
"@clappr/core": "^0.4.21",
|
||||
"@clappr/hlsjs-playback": "^0.6.0",
|
||||
"@clappr/plugins": "^0.4.16",
|
||||
"@clappr/stats-plugin": "^0.2.0",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@fontsource/dosis": "^4.5.9",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@mui/icons-material": "^5.10.2",
|
||||
"@mui/lab": "^5.0.0-alpha.96",
|
||||
"@mui/material": "5.1.1",
|
||||
"@mui/styles": "^5.1.1",
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@auth0/auth0-spa-js": "^2.1.3",
|
||||
"@babel/plugin-syntax-flow": "^7.24.7",
|
||||
"@babel/plugin-transform-react-jsx": "^7.25.2",
|
||||
"@emotion/react": "^11.13.3",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@fontsource/dosis": "^5.0.21",
|
||||
"@fontsource/roboto": "^5.0.14",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.6.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.6.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@lingui/core": "^4.11.4",
|
||||
"@lingui/macro": "^4.11.4",
|
||||
"@lingui/react": "^4.11.4",
|
||||
"@mui/icons-material": "^6.0.1",
|
||||
"@mui/lab": "^6.0.0-beta.8",
|
||||
"@mui/material": "^6.0.1",
|
||||
"@mui/styles": "^6.0.1",
|
||||
"@testing-library/dom": "^10.4.0",
|
||||
"@testing-library/jest-dom": "^6.5.0",
|
||||
"@testing-library/react": "^16.0.1",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^18.3.5",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"hls.js": "^0.14.17",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"make-plural": "^7.1.0",
|
||||
"react": "^17.0.2",
|
||||
"eslint": "^9.9.1",
|
||||
"handlebars": "^4.7.8",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"make-plural": "^7.4.0",
|
||||
"react": "^18.3.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"typescript": "^3.9.7",
|
||||
"react-device-detect": "^2.2.3",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-router-dom": "^6.26.1",
|
||||
"react-scripts": "^5.0.1",
|
||||
"semver": "^7.6.3",
|
||||
"serve": "^14.2.3",
|
||||
"typescript": "^5.5.4",
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^8.3.2",
|
||||
"video.js": "^7.20.2",
|
||||
"videojs-overlay": "^2.1.5"
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^10.0.0",
|
||||
"video.js": "^8.17.3",
|
||||
"videojs-overlay": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts --optimize-for-size build",
|
||||
"start-build": "serve -s build",
|
||||
"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",
|
||||
"i18n-extract": "lingui extract",
|
||||
"i18n-extract:clean": "lingui extract --clean",
|
||||
@ -73,7 +77,7 @@
|
||||
},
|
||||
"browserslist": {
|
||||
"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": [
|
||||
"last 1 chrome version",
|
||||
@ -82,14 +86,13 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.13",
|
||||
"@lingui/cli": "^3.14.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"prettier": "^2.7.1",
|
||||
"@babel/core": "^7.25.2",
|
||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||
"@lingui/cli": "^4.11.4",
|
||||
"babel-core": "^6.26.3",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"prettier": "^3.3.3",
|
||||
"react-error-overlay": "^6.0.11"
|
||||
},
|
||||
"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"
|
||||
}
|
||||
"resolutions": {}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
85
public/_player/clappr/dist/clappr.min.js
vendored
85
public/_player/clappr/dist/clappr.min.js
vendored
File diff suppressed because one or more lines are too long
1
public/_player/clappr/dist/clappr.min.js.map
vendored
1
public/_player/clappr/dist/clappr.min.js.map
vendored
File diff suppressed because one or more lines are too long
@ -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>
|
||||
37
public/_player/videojs/dist/video-js-skin.css
vendored
37
public/_player/videojs/dist/video-js-skin.css
vendored
@ -95,7 +95,7 @@
|
||||
/* volume-panel */
|
||||
|
||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||
padding-top: 1em;
|
||||
padding-top: .9em;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-control .vjs-volume-panel {
|
||||
@ -110,8 +110,8 @@
|
||||
|
||||
/* disable caps */
|
||||
|
||||
.vjs-internal .vjs-subs-caps-button {
|
||||
display: none;
|
||||
.vjs-public .vjs-subs-caps-button {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
/* spacer */
|
||||
@ -123,33 +123,37 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.vjs-public .vjs-overlay > a > img {
|
||||
width: 100%;
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 25%!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 28%!important;
|
||||
max-height: 28%!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: 20px!important;
|
||||
left: 30px!important;
|
||||
top: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-right {
|
||||
top: 20px!important;
|
||||
right: 30px!important;
|
||||
top: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-left {
|
||||
bottom: 20px!important;
|
||||
left: 30px!important;
|
||||
bottom: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-right {
|
||||
bottom: 20px!important;
|
||||
right: 30px!important;
|
||||
bottom: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* context menu */
|
||||
@ -166,4 +170,3 @@
|
||||
.vjs-public .vjs-lock-open {
|
||||
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
778
public/_player/videojs/dist/video-js.css
vendored
File diff suppressed because one or more lines are too long
2
public/_player/videojs/dist/video-js.min.css
vendored
2
public/_player/videojs/dist/video-js.min.css
vendored
File diff suppressed because one or more lines are too long
46801
public/_player/videojs/dist/video.js
vendored
46801
public/_player/videojs/dist/video.js
vendored
File diff suppressed because one or more lines are too long
56
public/_player/videojs/dist/video.min.js
vendored
56
public/_player/videojs/dist/video.min.js
vendored
File diff suppressed because one or more lines are too long
1
public/_player/videojs/dist/videojs-airplay.css
vendored
Normal file
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
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 +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:20px;height:20px}.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-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}
|
||||
1317
public/_player/videojs/dist/videojs-airplay.min.js
vendored
1317
public/_player/videojs/dist/videojs-airplay.min.js
vendored
File diff suppressed because it is too large
Load Diff
@ -1,65 +1 @@
|
||||
/** Silvermine Chromecast **/
|
||||
.vjs-chromecast-button .vjs-icon-placeholder {
|
||||
background: url('ic_cast_white_24dp.png') center center no-repeat;
|
||||
background-size: contain;
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
.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-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: #cccccc;
|
||||
position: absolute;
|
||||
left: calc(50% - 50px);
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-poster-img {
|
||||
max-height: 180px;
|
||||
width: auto;
|
||||
border: 2px solid #cccccc;
|
||||
}
|
||||
.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: #cccccc;
|
||||
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: 0.5em;
|
||||
}
|
||||
.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty {
|
||||
display: none;
|
||||
}
|
||||
.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
4100
public/_player/videojs/dist/videojs-chromecast.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
@ -1 +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:20px;height:20px}.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-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}
|
||||
.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}
|
||||
3149
public/_player/videojs/dist/videojs-chromecast.min.js
vendored
3149
public/_player/videojs/dist/videojs-chromecast.min.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
795
public/_player/videojs/dist/videojs-license.js
vendored
795
public/_player/videojs/dist/videojs-license.js
vendored
@ -1,459 +1,340 @@
|
||||
/*! @name videojs-license @version 0.1.0 @license MIT */
|
||||
(function (global, factory) {
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js'), require('global/document')) :
|
||||
typeof define === 'function' && define.amd ? define(['video.js', 'global/document'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs, global.document));
|
||||
}(this, (function (videojs, document) { 'use strict';
|
||||
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||
var document__default = /*#__PURE__*/_interopDefaultLegacy(document);
|
||||
|
||||
function createCommonjsModule(fn, basedir, module) {
|
||||
return module = {
|
||||
path: basedir,
|
||||
exports: {},
|
||||
require: function (path, base) {
|
||||
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
|
||||
}
|
||||
}, fn(module, module.exports), module.exports;
|
||||
}
|
||||
|
||||
function commonjsRequire () {
|
||||
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
|
||||
}
|
||||
|
||||
var assertThisInitialized = createCommonjsModule(function (module) {
|
||||
function _assertThisInitialized(self) {
|
||||
if (self === void 0) {
|
||||
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
module.exports = _assertThisInitialized;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
var setPrototypeOf = createCommonjsModule(function (module) {
|
||||
function _setPrototypeOf(o, p) {
|
||||
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||
o.__proto__ = p;
|
||||
return o;
|
||||
};
|
||||
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
return _setPrototypeOf(o, p);
|
||||
}
|
||||
|
||||
module.exports = _setPrototypeOf;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
var inheritsLoose = createCommonjsModule(function (module) {
|
||||
function _inheritsLoose(subClass, superClass) {
|
||||
subClass.prototype = Object.create(superClass.prototype);
|
||||
subClass.prototype.constructor = subClass;
|
||||
setPrototypeOf(subClass, superClass);
|
||||
}
|
||||
|
||||
module.exports = _inheritsLoose;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
var version = "0.1.0";
|
||||
|
||||
var Plugin = videojs__default['default'].getPlugin('plugin');
|
||||
var Component = videojs__default['default'].getComponent('Component');
|
||||
var Button = videojs__default['default'].getComponent('MenuButton'); // Default options for the plugin.
|
||||
|
||||
var defaults = {
|
||||
license: 'none',
|
||||
title: '',
|
||||
author: '',
|
||||
languages: {
|
||||
license: 'License',
|
||||
loading: 'Loading'
|
||||
}
|
||||
};
|
||||
/**
|
||||
* An advanced Video.js plugin. For more information on the API
|
||||
*
|
||||
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
|
||||
*/
|
||||
|
||||
var License = /*#__PURE__*/function (_Plugin) {
|
||||
inheritsLoose(License, _Plugin);
|
||||
|
||||
/**
|
||||
* Create a License plugin instance.
|
||||
*
|
||||
* @param {Player} player
|
||||
* A Video.js Player instance.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* An optional options object.
|
||||
*
|
||||
* While not a core part of the Video.js plugin architecture, a
|
||||
* second argument of options is a convenient way to accept inputs
|
||||
* from your plugin's caller.
|
||||
*/
|
||||
function License(player, options) {
|
||||
var _this;
|
||||
|
||||
// the parent class will add player under this.player
|
||||
_this = _Plugin.call(this, player) || this;
|
||||
_this.playerId = _this.player.id();
|
||||
_this.options = videojs__default['default'].mergeOptions(defaults, options);
|
||||
|
||||
if (options.license === 'none') {
|
||||
return assertThisInitialized(_this);
|
||||
}
|
||||
|
||||
_this.player.ready(function () {
|
||||
_this.player.addClass('vjs-license');
|
||||
|
||||
_this.buildUI();
|
||||
|
||||
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||
_this.mobileBuildUI();
|
||||
}
|
||||
}); // close the menu if open on userinactive
|
||||
|
||||
|
||||
_this.player.on('userinactive', function () {
|
||||
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
||||
element.classList.remove('vjs-lock-open');
|
||||
});
|
||||
}); // close the menu if anywhere in the player is clicked
|
||||
|
||||
|
||||
_this.player.on('click', function (evt) {
|
||||
if (evt.target.tagName === 'VIDEO') {
|
||||
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
|
||||
element.classList.remove('vjs-lock-open');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
_this.player.on('loadstart', function (_event) {
|
||||
_this.removeElementsByClass('vjs-license-clear');
|
||||
|
||||
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||
_this.mobileBuildTopLevelMenu();
|
||||
} else {
|
||||
_this.buildTopLevelMenu();
|
||||
}
|
||||
});
|
||||
|
||||
return _this;
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
|
||||
|
||||
var _proto = License.prototype;
|
||||
|
||||
_proto.buildUI = function buildUI() {
|
||||
var playerId = this.playerId;
|
||||
var that = this;
|
||||
/**
|
||||
* LicenseMenuButton
|
||||
*/
|
||||
|
||||
var LicenseMenuButton = /*#__PURE__*/function (_Button) {
|
||||
inheritsLoose(LicenseMenuButton, _Button);
|
||||
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param {*} player videojs player instance
|
||||
* @param {*} options videojs player options
|
||||
*/
|
||||
function LicenseMenuButton(player, options) {
|
||||
var _this2;
|
||||
|
||||
_this2 = _Button.call(this, player, options) || this;
|
||||
|
||||
_this2.addClass('vjs-license');
|
||||
|
||||
_this2.controlText(that.options.languages.loading);
|
||||
|
||||
player.one('canplaythrough', function (_event) {
|
||||
_this2.controlText(that.options.languages.settings);
|
||||
});
|
||||
_this2.menu.contentEl_.id = playerId + '-vjs-license-default';
|
||||
return _this2;
|
||||
}
|
||||
/**
|
||||
* Handle click
|
||||
*/
|
||||
|
||||
|
||||
var _proto2 = LicenseMenuButton.prototype;
|
||||
|
||||
_proto2.handleClick = function handleClick() {
|
||||
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
|
||||
} else {
|
||||
this.el().classList.toggle('vjs-toogle-btn');
|
||||
this.menu.el().classList.toggle('vjs-lock-open');
|
||||
}
|
||||
};
|
||||
|
||||
return LicenseMenuButton;
|
||||
}(Button);
|
||||
|
||||
videojs__default['default'].registerComponent('licenseMenuButton', LicenseMenuButton);
|
||||
this.player.getChild('controlBar').addChild('licenseMenuButton');
|
||||
|
||||
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
|
||||
this.player.getChild('controlBar').el().insertBefore(this.player.getChild('controlBar').getChild('licenseMenuButton').el(), this.player.getChild('controlBar').getChild('fullscreenToggle').el());
|
||||
}
|
||||
}
|
||||
/**
|
||||
*
|
||||
* This is just build the top level menu no sub menus
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.buildTopLevelMenu = function buildTopLevelMenu() {
|
||||
var settingsButton = this.player.getChild('controlBar').getChild('licenseMenuButton'); // settingsButton.addClass('vjs-license-is-loaded');
|
||||
|
||||
var main = settingsButton.menu.contentEl_; // Empty the main menu div to repopulate
|
||||
|
||||
main.innerHTML = '';
|
||||
main.classList.add('vjs-license-top-level'); // Start building new list items
|
||||
|
||||
var menuTitle = document__default['default'].createElement('li');
|
||||
menuTitle.className = 'vjs-license-top-level-header';
|
||||
var menuTitleInner = document__default['default'].createElement('span');
|
||||
menuTitleInner.innerHTML = 'About';
|
||||
menuTitleInner.className = 'vjs-license-top-level-header-titel';
|
||||
menuTitle.appendChild(menuTitleInner);
|
||||
main.appendChild(menuTitle);
|
||||
var itemTitel = document__default['default'].createElement('li');
|
||||
itemTitel.innerHTML = this.buildItemTitel();
|
||||
itemTitel.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemTitel);
|
||||
|
||||
if (this.options.author) {
|
||||
var itemAuthor = document__default['default'].createElement('li');
|
||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||
itemAuthor.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemAuthor);
|
||||
}
|
||||
|
||||
var itemLicense = document__default['default'].createElement('li');
|
||||
itemLicense.innerHTML = this.buildItemLicense();
|
||||
itemLicense.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemLicense);
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.mobileBuildUI = function mobileBuildUI() {
|
||||
/**
|
||||
* bla
|
||||
*/
|
||||
var LicenseMenuMobileModal = /*#__PURE__*/function (_Component) {
|
||||
inheritsLoose(LicenseMenuMobileModal, _Component);
|
||||
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param {*} player videojs player instance
|
||||
* @param {*} options videojs player options
|
||||
*/
|
||||
function LicenseMenuMobileModal(player, options) {
|
||||
return _Component.call(this, player, options) || this;
|
||||
}
|
||||
/**
|
||||
* Creates an HTML element
|
||||
*
|
||||
* @return {Object} HTML element
|
||||
*/
|
||||
|
||||
|
||||
var _proto3 = LicenseMenuMobileModal.prototype;
|
||||
|
||||
_proto3.createEl = function createEl() {
|
||||
return videojs__default['default'].createEl('div', {
|
||||
className: 'vjs-license-mobile'
|
||||
});
|
||||
};
|
||||
|
||||
return LicenseMenuMobileModal;
|
||||
}(Component);
|
||||
|
||||
videojs__default['default'].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
|
||||
videojs__default['default'].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document__default['default'].body);
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.mobileBuildTopLevelMenu = function mobileBuildTopLevelMenu() {
|
||||
var _this3 = this;
|
||||
|
||||
var settingsButton = this.player.getChild('licenseMenuMobileModal');
|
||||
var menuTopLevel = document__default['default'].createElement('ul');
|
||||
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
|
||||
settingsButton.el().appendChild(menuTopLevel); // Empty the main menu div to repopulate
|
||||
|
||||
var menuTitle = document__default['default'].createElement('li');
|
||||
menuTitle.innerHTML = 'About';
|
||||
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
|
||||
menuTopLevel.appendChild(menuTitle);
|
||||
var itemTitel = document__default['default'].createElement('li');
|
||||
itemTitel.innerHTML = this.buildItemTitel();
|
||||
itemTitel.className = 'vjs-license-top-level-item';
|
||||
|
||||
if (this.options.author) {
|
||||
var itemAuthor = document__default['default'].createElement('li');
|
||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||
itemAuthor.className = 'vjs-license-top-level-item';
|
||||
}
|
||||
|
||||
var itemLicense = document__default['default'].createElement('li');
|
||||
itemLicense.innerHTML = this.buildItemLicense();
|
||||
itemLicense.className = 'vjs-license-top-level-item';
|
||||
var menuClose = document__default['default'].createElement('li');
|
||||
menuClose.innerHTML = 'Close';
|
||||
menuClose.className = 'setting-menu-footer-default';
|
||||
|
||||
menuClose.onclick = function (e) {
|
||||
_this3.player.getChild('settingsMenuMobileModal').el().style.display = 'none';
|
||||
};
|
||||
|
||||
menuTopLevel.appendChild(menuClose);
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.buildItemTitel = function buildItemTitel() {
|
||||
var titel = '';
|
||||
|
||||
if (this.options.title) {
|
||||
titel = "" + this.options.title;
|
||||
}
|
||||
|
||||
return 'Title: ' + titel;
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.buildItemAuthor = function buildItemAuthor() {
|
||||
var author = '';
|
||||
|
||||
if (this.options.author) {
|
||||
author = " by " + this.options.author;
|
||||
}
|
||||
|
||||
return 'Author: ' + author;
|
||||
}
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.buildItemLicense = function buildItemLicense() {
|
||||
var license = '';
|
||||
var reVersion = new RegExp('[0-9]+.[0-9]+$');
|
||||
var version = '4.0';
|
||||
var matches = this.options.license.match(reVersion);
|
||||
|
||||
if (matches !== null) {
|
||||
version = matches[0];
|
||||
}
|
||||
|
||||
var which = this.options.license.replace(reVersion, '').trim();
|
||||
var deed = null;
|
||||
|
||||
switch (which) {
|
||||
case 'CC0':
|
||||
deed = 'https://creativecommons.org/licenses/zero/1.0/';
|
||||
break;
|
||||
|
||||
case 'CC BY':
|
||||
deed = "https://creativecommons.org/licenses/by/" + version + "/";
|
||||
break;
|
||||
|
||||
case 'CC BY-SA':
|
||||
deed = "https://creativecommons.org/licenses/by-sa/" + version + "/";
|
||||
break;
|
||||
|
||||
case 'CC BY-NC':
|
||||
deed = "https://creativecommons.org/licenses/by-nc/" + version + "/";
|
||||
break;
|
||||
|
||||
case 'CC BY-NC-SA':
|
||||
deed = "https://creativecommons.org/licenses/by-nc-sa/" + version + "/";
|
||||
break;
|
||||
|
||||
case 'CC BY-ND':
|
||||
deed = "https://creativecommons.org/licenses/by-nd/" + version + "/";
|
||||
break;
|
||||
|
||||
case 'CC BY-NC-ND':
|
||||
deed = "https://creativecommons.org/licenses/by-nc-nd/" + version + "/";
|
||||
break;
|
||||
}
|
||||
|
||||
if (deed) {
|
||||
license = "<a href=\"" + deed + "\" onclick=\"window.open('" + deed + "')\" target=\"_blank\" rel=\"noopener\">" + this.options.license + "</a>";
|
||||
} else {
|
||||
license = this.options.license;
|
||||
}
|
||||
|
||||
return 'License: ' + license;
|
||||
}
|
||||
/**
|
||||
*
|
||||
* Helper class to clear menu items before rebuild
|
||||
*
|
||||
* @param {*} className Name of a class
|
||||
*/
|
||||
;
|
||||
|
||||
_proto.removeElementsByClass = function removeElementsByClass(className) {
|
||||
// Need to prevent the menu from not showing sometimes
|
||||
document__default['default'].querySelectorAll('.vjs-sm-top-level').forEach(function (element) {
|
||||
element.classList.remove('vjs-hidden');
|
||||
});
|
||||
var elements = document__default['default'].getElementsByClassName(className);
|
||||
|
||||
while (elements.length > 0) {
|
||||
elements[0].parentNode.removeChild(elements[0]);
|
||||
}
|
||||
};
|
||||
|
||||
return License;
|
||||
}(Plugin); // Define default values for the plugin's `state` object here.
|
||||
|
||||
|
||||
License.defaultState = {}; // Include the version number.
|
||||
|
||||
License.VERSION = version; // Register the plugin with video.js.
|
||||
|
||||
videojs__default['default'].registerPlugin('license', License);
|
||||
|
||||
return License;
|
||||
|
||||
})));
|
||||
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
|
||||
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
|
||||
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs));
|
||||
})(this, (function (videojs) { 'use strict';
|
||||
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||
|
||||
var version = "0.1.0";
|
||||
|
||||
const Plugin = videojs__default["default"].getPlugin('plugin');
|
||||
const Component = videojs__default["default"].getComponent('Component');
|
||||
const Button = videojs__default["default"].getComponent('MenuButton');
|
||||
|
||||
// Default options for the plugin.
|
||||
const defaults = {
|
||||
license: 'none',
|
||||
title: '',
|
||||
author: '',
|
||||
languages: {
|
||||
license: 'License',
|
||||
loading: 'Loading'
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An advanced Video.js plugin. For more information on the API
|
||||
*
|
||||
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
|
||||
*/
|
||||
class License extends Plugin {
|
||||
/**
|
||||
* Create a License plugin instance.
|
||||
*
|
||||
* @param {Player} player
|
||||
* A Video.js Player instance.
|
||||
*
|
||||
* @param {Object} [options]
|
||||
* An optional options object.
|
||||
*
|
||||
* While not a core part of the Video.js plugin architecture, a
|
||||
* second argument of options is a convenient way to accept inputs
|
||||
* from your plugin's caller.
|
||||
*/
|
||||
constructor(player, options) {
|
||||
// the parent class will add player under this.player
|
||||
super(player);
|
||||
this.playerId = this.player.id();
|
||||
this.options = videojs__default["default"].mergeOptions(defaults, options);
|
||||
if (options.license === 'none') {
|
||||
return;
|
||||
}
|
||||
player.addClass('vjs-license');
|
||||
this.buildUI();
|
||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
||||
this.mobileBuildUI();
|
||||
}
|
||||
|
||||
// close the menu if open on userinactive
|
||||
this.player.on('userinactive', () => {
|
||||
document.getElementById(this.playerId).querySelectorAll('.vjs-menu').forEach(element => {
|
||||
element.classList.remove('vjs-lock-open');
|
||||
});
|
||||
});
|
||||
|
||||
// close the menu if anywhere in the player is clicked
|
||||
this.player.on('click', evt => {
|
||||
if (evt.target.tagName === 'VIDEO') {
|
||||
document.getElementById(this.playerId).querySelectorAll('.vjs-menu').forEach(element => {
|
||||
element.classList.remove('vjs-lock-open');
|
||||
});
|
||||
}
|
||||
});
|
||||
this.player.on('loadstart', _event => {
|
||||
this.removeElementsByClass('vjs-license-clear');
|
||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
||||
this.mobileBuildTopLevelMenu();
|
||||
} else {
|
||||
this.buildTopLevelMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
buildUI() {
|
||||
const playerId = this.playerId;
|
||||
const that = this;
|
||||
|
||||
/**
|
||||
* LicenseMenuButton
|
||||
*/
|
||||
class LicenseMenuButton extends Button {
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param {*} player videojs player instance
|
||||
* @param {*} options videojs player options
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
this.addClass('vjs-license');
|
||||
this.controlText(that.options.languages.loading);
|
||||
player.one('canplaythrough', _event => {
|
||||
this.controlText(that.options.languages.settings);
|
||||
});
|
||||
this.menu.contentEl_.id = playerId + '-vjs-license-default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle click
|
||||
*/
|
||||
handleClick() {
|
||||
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
|
||||
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
|
||||
} else {
|
||||
this.el().classList.toggle('vjs-toogle-btn');
|
||||
this.menu.el().classList.toggle('vjs-lock-open');
|
||||
}
|
||||
}
|
||||
}
|
||||
videojs__default["default"].registerComponent('licenseMenuButton', LicenseMenuButton);
|
||||
this.player.getChild('controlBar').addChild('licenseMenuButton');
|
||||
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
|
||||
this.player.getChild('controlBar').el().insertBefore(this.player.getChild('controlBar').getChild('licenseMenuButton').el(), this.player.getChild('controlBar').getChild('fullscreenToggle').el());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This is just build the top level menu no sub menus
|
||||
*/
|
||||
buildTopLevelMenu() {
|
||||
const settingsButton = this.player.getChild('controlBar').getChild('licenseMenuButton');
|
||||
|
||||
// settingsButton.addClass('vjs-license-is-loaded');
|
||||
|
||||
const main = settingsButton.menu.contentEl_;
|
||||
|
||||
// Empty the main menu div to repopulate
|
||||
main.innerHTML = '';
|
||||
main.classList.add('vjs-license-top-level');
|
||||
|
||||
// Start building new list items
|
||||
const menuTitle = document.createElement('li');
|
||||
menuTitle.className = 'vjs-license-top-level-header';
|
||||
const menuTitleInner = document.createElement('span');
|
||||
menuTitleInner.innerHTML = 'About';
|
||||
menuTitleInner.className = 'vjs-license-top-level-header-titel';
|
||||
menuTitle.appendChild(menuTitleInner);
|
||||
main.appendChild(menuTitle);
|
||||
const itemTitel = document.createElement('li');
|
||||
itemTitel.innerHTML = this.buildItemTitel();
|
||||
itemTitel.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemTitel);
|
||||
if (this.options.author) {
|
||||
const itemAuthor = document.createElement('li');
|
||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||
itemAuthor.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemAuthor);
|
||||
}
|
||||
const itemLicense = document.createElement('li');
|
||||
itemLicense.innerHTML = this.buildItemLicense();
|
||||
itemLicense.className = 'vjs-license-top-level-item';
|
||||
main.appendChild(itemLicense);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
mobileBuildUI() {
|
||||
/**
|
||||
* bla
|
||||
*/
|
||||
class LicenseMenuMobileModal extends Component {
|
||||
/**
|
||||
* Contructor
|
||||
*
|
||||
* @param {*} player videojs player instance
|
||||
* @param {*} options videojs player options
|
||||
*/
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an HTML element
|
||||
*
|
||||
* @return {Object} HTML element
|
||||
*/
|
||||
createEl() {
|
||||
return videojs__default["default"].createEl('div', {
|
||||
className: 'vjs-license-mobile'
|
||||
});
|
||||
}
|
||||
}
|
||||
videojs__default["default"].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
|
||||
videojs__default["default"].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document.body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
mobileBuildTopLevelMenu() {
|
||||
const settingsButton = this.player.getChild('licenseMenuMobileModal');
|
||||
const menuTopLevel = document.createElement('ul');
|
||||
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
|
||||
settingsButton.el().appendChild(menuTopLevel);
|
||||
|
||||
// Empty the main menu div to repopulate
|
||||
const menuTitle = document.createElement('li');
|
||||
menuTitle.innerHTML = 'About';
|
||||
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
|
||||
menuTopLevel.appendChild(menuTitle);
|
||||
const itemTitel = document.createElement('li');
|
||||
itemTitel.innerHTML = this.buildItemTitel();
|
||||
itemTitel.className = 'vjs-license-top-level-item';
|
||||
if (this.options.author) {
|
||||
const itemAuthor = document.createElement('li');
|
||||
itemAuthor.innerHTML = this.buildItemAuthor();
|
||||
itemAuthor.className = 'vjs-license-top-level-item';
|
||||
}
|
||||
const itemLicense = document.createElement('li');
|
||||
itemLicense.innerHTML = this.buildItemLicense();
|
||||
itemLicense.className = 'vjs-license-top-level-item';
|
||||
const menuClose = document.createElement('li');
|
||||
menuClose.innerHTML = 'Close';
|
||||
menuClose.className = 'setting-menu-footer-default';
|
||||
menuClose.onclick = e => {
|
||||
this.player.getChild('settingsMenuMobileModal').el().style.display = 'none';
|
||||
};
|
||||
menuTopLevel.appendChild(menuClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
buildItemTitel() {
|
||||
let titel = '';
|
||||
if (this.options.title) {
|
||||
titel = `${this.options.title}`;
|
||||
}
|
||||
return 'Title: ' + titel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
buildItemAuthor() {
|
||||
let author = '';
|
||||
if (this.options.author) {
|
||||
author = ` by ${this.options.author}`;
|
||||
}
|
||||
return 'Author: ' + author;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*
|
||||
* @return {string} Returns license text
|
||||
*/
|
||||
buildItemLicense() {
|
||||
let license = '';
|
||||
const reVersion = new RegExp('[0-9]+.[0-9]+$');
|
||||
let version = '4.0';
|
||||
const matches = this.options.license.match(reVersion);
|
||||
if (matches !== null) {
|
||||
version = matches[0];
|
||||
}
|
||||
const which = this.options.license.replace(reVersion, '').trim();
|
||||
let deed = null;
|
||||
switch (which) {
|
||||
case 'CC0':
|
||||
deed = 'https://creativecommons.org/licenses/zero/1.0/';
|
||||
break;
|
||||
case 'CC BY':
|
||||
deed = `https://creativecommons.org/licenses/by/${version}/`;
|
||||
break;
|
||||
case 'CC BY-SA':
|
||||
deed = `https://creativecommons.org/licenses/by-sa/${version}/`;
|
||||
break;
|
||||
case 'CC BY-NC':
|
||||
deed = `https://creativecommons.org/licenses/by-nc/${version}/`;
|
||||
break;
|
||||
case 'CC BY-NC-SA':
|
||||
deed = `https://creativecommons.org/licenses/by-nc-sa/${version}/`;
|
||||
break;
|
||||
case 'CC BY-ND':
|
||||
deed = `https://creativecommons.org/licenses/by-nd/${version}/`;
|
||||
break;
|
||||
case 'CC BY-NC-ND':
|
||||
deed = `https://creativecommons.org/licenses/by-nc-nd/${version}/`;
|
||||
break;
|
||||
}
|
||||
if (deed) {
|
||||
license = `<a href='${deed}' onclick='window.open('${deed}')' target='_blank' rel='noopener'>${this.options.license}</a>`;
|
||||
} else {
|
||||
license = this.options.license;
|
||||
}
|
||||
return 'License: ' + license;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Helper class to clear menu items before rebuild
|
||||
*
|
||||
* @param {*} className Name of a class
|
||||
*/
|
||||
removeElementsByClass(className) {
|
||||
// Need to prevent the menu from not showing sometimes
|
||||
document.querySelectorAll('.vjs-sm-top-level').forEach(element => {
|
||||
element.classList.remove('vjs-hidden');
|
||||
});
|
||||
const elements = document.getElementsByClassName(className);
|
||||
while (elements.length > 0) {
|
||||
elements[0].parentNode.removeChild(elements[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Define default values for the plugin's `state` object here.
|
||||
License.defaultState = {};
|
||||
|
||||
// Include the version number.
|
||||
License.VERSION = version;
|
||||
|
||||
// Register the plugin with video.js.
|
||||
videojs__default["default"].registerPlugin('license', License);
|
||||
|
||||
return License;
|
||||
|
||||
}));
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -82,15 +82,14 @@
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
plugins: {
|
||||
license: playerConfig.license
|
||||
}
|
||||
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
};
|
||||
|
||||
if (playerConfig.chromecast) {
|
||||
config.techOrder = ["chromecast", "html5"];
|
||||
config.plugins.chromecast = {};
|
||||
config.plugins.chromecast = {
|
||||
receiverApplicationId: 'CC1AD845'
|
||||
};
|
||||
}
|
||||
|
||||
if (playerConfig.airplay) {
|
||||
@ -130,6 +129,8 @@
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
player.license(playerConfig.license);
|
||||
}
|
||||
|
||||
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();
|
||||
});
|
||||
@ -438,11 +438,11 @@
|
||||
</div>
|
||||
<div class="col-xs-12 channel-list-inside">
|
||||
{{#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="col-xs-12 middle-xs channel-list-l2">
|
||||
<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="channel-list-description">
|
||||
{{this.name}}
|
||||
@ -498,11 +498,11 @@
|
||||
</div>
|
||||
<div class="col-xs-12 col-sm-12">
|
||||
{{#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="col-xs-12 middle-xs channel-list-l2">
|
||||
<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="channel-list-description">
|
||||
{{this.name}}
|
||||
@ -619,6 +619,7 @@
|
||||
{{/if}}{{/if}}
|
||||
|
||||
<script>
|
||||
{{#if share}}
|
||||
var modal_share = document.getElementById("modal-share");
|
||||
var btn_share = document.getElementById("btn-share");
|
||||
var close_share = document.getElementById("close-share");
|
||||
@ -628,6 +629,7 @@
|
||||
close_share.onclick = function() {
|
||||
modal_share.style.display = "none";
|
||||
}
|
||||
{{/if}}
|
||||
{{#if imprint_html}}
|
||||
var modal_imprint = document.getElementById("modal-imprint");
|
||||
var btn_imprint = document.getElementById("btn-imprint");
|
||||
@ -664,7 +666,9 @@
|
||||
var close = document.getElementsByClassName("close")[0];
|
||||
|
||||
close.onclick = function() {
|
||||
{{#if share}}
|
||||
modal_share.style.display = "none";
|
||||
{{/if}}
|
||||
{{#if imprint_html}}
|
||||
modal_imprint.style.display = "none";
|
||||
{{/if}}
|
||||
@ -676,20 +680,29 @@
|
||||
{{/if}}{{/if}}
|
||||
}
|
||||
window.onclick = function(event) {
|
||||
if (event.target == modal_share) {
|
||||
modal_share.style.display = "none";
|
||||
{{#if imprint_html}}
|
||||
} else if (event.target == modal_imprint) {
|
||||
modal_imprint.style.display = "none";
|
||||
{{/if}}
|
||||
{{#if terms_html}}
|
||||
} else if (event.target == modal_terms) {
|
||||
modal_terms.style.display = "none";
|
||||
{{/if}}
|
||||
{{#if channel_creator_name}}{{#if channel_creator_description_html}}
|
||||
} else if (event.target == modal_creator) {
|
||||
modal_creator.style.display = "none";
|
||||
{{/if}}{{/if}}
|
||||
switch(event.target) {
|
||||
{{#if share}}
|
||||
case modal_share:
|
||||
modal_share.style.display = "none";
|
||||
break;
|
||||
{{/if}}
|
||||
{{#if imprint_html}}
|
||||
case modal_imprint:
|
||||
modal_imprint.style.display = "none";
|
||||
break;
|
||||
{{/if}}
|
||||
{{#if terms_html}}
|
||||
case modal_terms:
|
||||
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>
|
||||
@ -698,10 +711,10 @@
|
||||
var license = '{{channel_license}}';
|
||||
var license_url = '';
|
||||
var license_name = 'unknown';
|
||||
if (license === 'CC0 4.0') {
|
||||
if (license === 'CC0 1.0') {
|
||||
license_name = 'CC0 1.0 Universal'
|
||||
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') {
|
||||
license_name = 'CC BY 4.0'
|
||||
license_url = 'https://creativecommons.org/licenses/by/4.0/';
|
||||
@ -725,7 +738,7 @@
|
||||
} else if (license === '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_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_image").src = license_image;
|
||||
|
||||
@ -6,25 +6,32 @@ var config = {
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
// Needed to append the url orgin in order for the source to properly pass to the cast device
|
||||
// Needed to append the url origin in order for the source to properly pass to the cast device
|
||||
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
plugins: {
|
||||
license: playerConfig.license,
|
||||
},
|
||||
plugins: {},
|
||||
};
|
||||
|
||||
if (chromecast) {
|
||||
config.techOrder = ['chromecast', 'html5'];
|
||||
config.plugins.chromecast = {};
|
||||
}
|
||||
|
||||
if (airplay) {
|
||||
config.plugins.airPlay = {};
|
||||
// Provide a default reciever application ID
|
||||
config.plugins.chromecast = {
|
||||
receiverApplicationId: 'CC1AD845',
|
||||
};
|
||||
}
|
||||
|
||||
var player = videojs('player', config);
|
||||
|
||||
player.ready(function () {
|
||||
if (chromecast) {
|
||||
player.chromecast();
|
||||
}
|
||||
|
||||
if (airplay) {
|
||||
player.airPlay();
|
||||
}
|
||||
|
||||
player.license(playerConfig.license);
|
||||
|
||||
if (playerConfig.logo.image.length != 0) {
|
||||
var overlay = null;
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
<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" />
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
|
||||
@ -20,6 +20,6 @@
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
"theme_color": "#282728",
|
||||
"background_color": "#282728"
|
||||
}
|
||||
|
||||
54
src/I18n.js
54
src/I18n.js
@ -2,45 +2,63 @@ import React from 'react';
|
||||
|
||||
import { I18nProvider } from '@lingui/react';
|
||||
import { i18n } from '@lingui/core';
|
||||
import * as plurals from 'make-plural/plurals';
|
||||
|
||||
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 EL } from './locales/el/messages.js';
|
||||
import { messages as ES } from './locales/es/messages.js';
|
||||
import { messages as FR } from './locales/fr/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 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 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';
|
||||
|
||||
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({
|
||||
en: EN,
|
||||
da: DA,
|
||||
de: DE,
|
||||
el: EL,
|
||||
es: ES,
|
||||
fr: FR,
|
||||
it: IT,
|
||||
ko: KO,
|
||||
pl: PL,
|
||||
pt: PT,
|
||||
'pt-br': PT,
|
||||
ru: RU,
|
||||
sl: SL,
|
||||
tr: TR,
|
||||
uk: UK,
|
||||
'zh-hans': ZH,
|
||||
});
|
||||
|
||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
let lang = Storage.Get('language');
|
||||
if (supportedLanguages.indexOf(lang) === -1) {
|
||||
lang = getBrowserLanguage(defaultLanguage);
|
||||
const aliases = {
|
||||
pt: 'pt-br',
|
||||
'zh-cn': 'zh-hans',
|
||||
};
|
||||
|
||||
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) {
|
||||
lang = defaultLanguage;
|
||||
lang = getAlias(getBrowserLanguage(defaultLanguage));
|
||||
|
||||
if (supportedLanguages.indexOf(lang) === -1) {
|
||||
lang = defaultLanguage;
|
||||
}
|
||||
}
|
||||
|
||||
Storage.Set('language', lang);
|
||||
@ -51,7 +69,7 @@ const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
const getBrowserLanguage = (defaultLanguage) => {
|
||||
let lang = window.navigator.language;
|
||||
|
||||
const match = lang.match(/^[a-z]+/);
|
||||
const match = lang.match(/^[a-z]+(-[a-z]+)?/i);
|
||||
if (!match) {
|
||||
return defaultLanguage;
|
||||
}
|
||||
@ -59,7 +77,7 @@ const getBrowserLanguage = (defaultLanguage) => {
|
||||
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) {
|
||||
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
||||
|
||||
@ -14,9 +14,15 @@ import ChannelList from './misc/ChannelList';
|
||||
import Footer from './Footer';
|
||||
import I18n from './I18n';
|
||||
import Header from './Header';
|
||||
import * as M from './utils/metadata';
|
||||
import Restreamer from './utils/restreamer';
|
||||
import Router from './Router';
|
||||
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) => ({
|
||||
MainHeader: {
|
||||
@ -58,6 +64,12 @@ export default function RestreamerUI(props) {
|
||||
channelid: '',
|
||||
channels: [],
|
||||
});
|
||||
const [$metadata, setMetadata] = React.useState({});
|
||||
const [$changelog, setChangelog] = React.useState({
|
||||
open: false,
|
||||
current: '',
|
||||
previous: '',
|
||||
});
|
||||
|
||||
const restreamer = React.useRef(null);
|
||||
|
||||
@ -131,6 +143,8 @@ export default function RestreamerUI(props) {
|
||||
|
||||
const valid = await restreamer.current.Validate();
|
||||
|
||||
await checkChangelog();
|
||||
|
||||
setState({
|
||||
...$state,
|
||||
initialized: true,
|
||||
@ -146,8 +160,93 @@ export default function RestreamerUI(props) {
|
||||
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 connected = await restreamer.current.Login(username, password);
|
||||
|
||||
await checkChangelog();
|
||||
|
||||
setState({
|
||||
...$state,
|
||||
connected: connected,
|
||||
@ -203,6 +302,7 @@ export default function RestreamerUI(props) {
|
||||
enable: true,
|
||||
},
|
||||
},
|
||||
version: 3,
|
||||
};
|
||||
|
||||
if (username.length !== 0) {
|
||||
@ -356,38 +456,42 @@ export default function RestreamerUI(props) {
|
||||
return null;
|
||||
};
|
||||
|
||||
let view = <Views.Initializing />;
|
||||
if ($state.valid === false) {
|
||||
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')}
|
||||
/>
|
||||
);
|
||||
let view = null;
|
||||
if ($state.initialized === false) {
|
||||
view = <Views.Initializing />;
|
||||
} else {
|
||||
view = <Router restreamer={restreamer.current} />;
|
||||
resources = handleResources;
|
||||
if ($state.valid === false) {
|
||||
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;
|
||||
@ -444,6 +548,9 @@ export default function RestreamerUI(props) {
|
||||
onState={handleStateChannel}
|
||||
/>
|
||||
)}
|
||||
{expand && $changelog.open && (
|
||||
<Changelog open={$changelog.open} onClose={handleCloseChangelog} current={$changelog.current} previous={$changelog.previous} />
|
||||
)}
|
||||
</NotifyProvider>
|
||||
</I18n>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
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';
|
||||
|
||||
@ -11,9 +11,9 @@ export default function Router(props) {
|
||||
const channelid = props.restreamer.GetCurrentChannelID();
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<DOMRouter>
|
||||
<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="/settings" 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="*" render={() => <Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</DOMRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
10
src/index.js
10
src/index.js
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
|
||||
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
||||
import '@fontsource/dosis';
|
||||
@ -10,18 +10,20 @@ import theme from './theme';
|
||||
import RestreamerUI from './RestreamerUI';
|
||||
|
||||
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));
|
||||
if (urlParams.has('address') === true) {
|
||||
address = urlParams.get('address');
|
||||
}
|
||||
|
||||
ReactDOM.render(
|
||||
createRoot(document.getElementById('root')).render(
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<RestreamerUI address={address} />
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>,
|
||||
document.getElementById('root')
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
|
||||
1
src/locales/da/messages.js
Normal file
1
src/locales/da/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/da/messages.po
Normal file
3451
src/locales/da/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/locales/el/messages.js
Normal file
1
src/locales/el/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/el/messages.po
Normal file
3451
src/locales/el/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/locales/ko/messages.js
Normal file
1
src/locales/ko/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/ko/messages.po
Normal file
3451
src/locales/ko/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/locales/pt-br/messages.js
Normal file
1
src/locales/pt-br/messages.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
1
src/locales/sl/messages.js
Normal file
1
src/locales/sl/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/sl/messages.po
Normal file
3451
src/locales/sl/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
1
src/locales/tr/messages.js
Normal file
1
src/locales/tr/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/tr/messages.po
Normal file
3451
src/locales/tr/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
1
src/locales/uk/messages.js
Normal file
1
src/locales/uk/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/uk/messages.po
Normal file
3451
src/locales/uk/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
1
src/locales/zh-hans/messages.js
Normal file
1
src/locales/zh-hans/messages.js
Normal file
File diff suppressed because one or more lines are too long
3451
src/locales/zh-hans/messages.po
Normal file
3451
src/locales/zh-hans/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
@ -47,8 +47,8 @@ export default function Component(props) {
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
justifyContent={props.justifyContent}
|
||||
alignItems={props.alignItems}
|
||||
textAlign={props.textAlign}
|
||||
spacing={1}
|
||||
className={
|
||||
@ -64,4 +64,6 @@ export default function Component(props) {
|
||||
Component.defaultProps = {
|
||||
color: 'light',
|
||||
textAlign: 'left',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
197
src/misc/Changelog.js
Normal file
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 CloseIcon from '@mui/icons-material/Close';
|
||||
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 IconButton from '@mui/material/IconButton';
|
||||
import LensIcon from '@mui/icons-material/Lens';
|
||||
@ -119,7 +121,7 @@ const ImageBackdrop = styled('span')(({ theme }) => ({
|
||||
border: `2px solid ${theme.palette.primary.dark}`,
|
||||
}));
|
||||
|
||||
function ChannelButton(props) {
|
||||
function ChannelButton(props, largeChannelList) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -154,7 +156,7 @@ function ChannelButton(props) {
|
||||
}
|
||||
|
||||
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 }}>
|
||||
<Stack direction="column" spacing={0.5}>
|
||||
<Image
|
||||
@ -192,6 +194,21 @@ ChannelButton.defaultProps = {
|
||||
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) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
@ -205,8 +222,12 @@ export default function ChannelList(props) {
|
||||
open: false,
|
||||
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(() => {
|
||||
(async () => {
|
||||
@ -246,19 +267,40 @@ export default function ChannelList(props) {
|
||||
disabled={channelid === channel.channelid}
|
||||
onClick={() => {
|
||||
onClick(channel.channelid);
|
||||
if ($largeChannelList) {
|
||||
onClose();
|
||||
}
|
||||
}}
|
||||
largeChannelList={$largeChannelList}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
setChannels(channels);
|
||||
})();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [$pos, allChannels, $nChannels, channelid, onClick, onState]);
|
||||
|
||||
const onMount = async () => {
|
||||
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 = () => {
|
||||
setAddChannel({
|
||||
...$addChannel,
|
||||
@ -282,6 +324,11 @@ export default function ChannelList(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleLargeChannelList = () => {
|
||||
setLargeChannelList(!$largeChannelList);
|
||||
setPos(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SwipeableDrawer
|
||||
@ -289,7 +336,13 @@ export default function ChannelList(props) {
|
||||
open={props.open}
|
||||
onOpen={() => {}}
|
||||
onClose={props.onClose}
|
||||
sx={{ marginButtom: 60 }}
|
||||
sx={{
|
||||
marginButtom: 60,
|
||||
'& .MuiDrawer-paper': {
|
||||
top: $largeChannelList ? '0px!important' : 'auto!important',
|
||||
height: $largeChannelList ? '100vh' : 'auto',
|
||||
},
|
||||
}}
|
||||
BackdropProps={{ invisible: true }}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
@ -307,6 +360,9 @@ export default function ChannelList(props) {
|
||||
<IconButton color="inherit" size="large" onClick={handleAddChannelDialog}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
<IconButton color="inherit" size="large" onClick={handleLargeChannelList}>
|
||||
{$largeChannelList ? <FullscreenExitIcon /> : <FullscreenIcon />}
|
||||
</IconButton>
|
||||
<IconButton color="inherit" size="large" onClick={props.onClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
@ -327,7 +383,7 @@ export default function ChannelList(props) {
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={0} className={classes.channelItems}>
|
||||
<Grid container spacing={0} justifyContent="center">
|
||||
<Grid container spacing={0} justifyContent={$largeChannelList ? 'flex-start' : 'center'}>
|
||||
{$channels}
|
||||
</Grid>
|
||||
</Stack>
|
||||
|
||||
@ -16,9 +16,19 @@ export default function EncodingSelect(props) {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
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 decoder = profile.decoder;
|
||||
const stream = props.streams[profile.stream];
|
||||
decoder.coder = event.target.value;
|
||||
|
||||
// If the coder changes, use the coder's default settings
|
||||
@ -30,7 +40,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults();
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
decoder.settings = defaults.settings;
|
||||
decoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -49,6 +59,7 @@ export default function EncodingSelect(props) {
|
||||
|
||||
const handleEncoderChange = (event) => {
|
||||
const encoder = profile.encoder;
|
||||
const stream = props.streams[profile.stream];
|
||||
encoder.coder = event.target.value;
|
||||
|
||||
// If the coder changes, use the coder's default settings
|
||||
@ -60,7 +71,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults({});
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
encoder.settings = defaults.settings;
|
||||
encoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -116,10 +127,10 @@ export default function EncodingSelect(props) {
|
||||
let encoderSettingsHelp = null;
|
||||
|
||||
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;
|
||||
|
||||
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)) {
|
||||
encoderSettingsHelp = handleEncoderHelp(coder.coder);
|
||||
@ -130,7 +141,7 @@ export default function EncodingSelect(props) {
|
||||
|
||||
for (let c of encoderRegistry.List()) {
|
||||
// Does ffmpeg support the coder?
|
||||
if (!props.availableEncoders.includes(c.coder)) {
|
||||
if (!availableEncoders.includes(c.coder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -144,14 +155,14 @@ export default function EncodingSelect(props) {
|
||||
encoderList.push(
|
||||
<MenuItem value={c.coder} key={c.coder}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
encoderList.push(
|
||||
<MenuItem value={c.coder} key={c.coder}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -173,22 +184,24 @@ export default function EncodingSelect(props) {
|
||||
|
||||
if (coder.coder !== 'copy' && coder.coder !== 'none') {
|
||||
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;
|
||||
|
||||
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
|
||||
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, props.availableDecoders, 'any')) {
|
||||
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, availableDecoders, 'any')) {
|
||||
decoderList.push(
|
||||
<MenuItem value={c.coder} key={c.coder}>
|
||||
{c.name}
|
||||
</MenuItem>
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in case there's no decoder for a codec it should be mentioned.
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
@ -237,7 +250,6 @@ EncodingSelect.defaultProps = {
|
||||
streams: [],
|
||||
profile: {},
|
||||
codecs: [],
|
||||
availableEncoders: [],
|
||||
availableDecoders: [],
|
||||
skills: {},
|
||||
onChange: function (encoder, decoder, automatic) {},
|
||||
};
|
||||
|
||||
28
src/misc/Filesize.js
Normal file
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,
|
||||
};
|
||||
@ -91,7 +91,7 @@ export default function FilterSelect(props) {
|
||||
}
|
||||
|
||||
filterSettings.push(
|
||||
<Settings key={c.filter} settings={profile.filter.settings[c.filter].settings} onChange={handleFilterSettingsChange(c.filter)} />
|
||||
<Settings key={c.filter} settings={profile.filter.settings[c.filter].settings} onChange={handleFilterSettingsChange(c.filter)} />,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -42,14 +42,21 @@ export default function LanguageSelect(props) {
|
||||
|
||||
return (
|
||||
<Select className={classes.root} variant="standard" displayEmpty value={i18n.locale} onChange={handleChange}>
|
||||
<MenuItem value="en">English </MenuItem>
|
||||
<MenuItem value="de">Deutsch </MenuItem>
|
||||
<MenuItem value="es">Español </MenuItem>
|
||||
<MenuItem value="fr">Français </MenuItem>
|
||||
<MenuItem value="it">Italiano </MenuItem>
|
||||
<MenuItem value="en">English</MenuItem>
|
||||
<MenuItem value="da">Dansk</MenuItem>
|
||||
<MenuItem value="de">Deutsch</MenuItem>
|
||||
<MenuItem value="el">Ελληνικά</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="pt">Português </MenuItem>
|
||||
<MenuItem value="ru">Русский </MenuItem>
|
||||
<MenuItem value="pt-br">Português</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>
|
||||
);
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.1 KiB |
File diff suppressed because one or more lines are too long
|
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) {
|
||||
return (
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<FormControl variant={props.variant} disabled={props.disabled} fullWidth>
|
||||
<InputLabel>{props.label}</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
input={<OutlinedInput />}
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
<Select multiple value={props.value} onChange={props.onChange} input={<OutlinedInput />} renderValue={props.renderValue} MenuProps={MenuProps}>
|
||||
{props.children}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@ -32,7 +25,10 @@ export default function Component(props) {
|
||||
}
|
||||
|
||||
Component.defaultProps = {
|
||||
variant: 'outlined',
|
||||
label: '',
|
||||
value: [],
|
||||
disabled: false,
|
||||
renderValue: (selected) => selected.join(', '),
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
@ -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 Clappr from './clappr';
|
||||
import VideoJS from './videojs';
|
||||
|
||||
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') {
|
||||
const config = {
|
||||
@ -16,7 +15,7 @@ export default function Player(props) {
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
plugins: {
|
||||
reloadSourceOnError: {}
|
||||
reloadSourceOnError: {},
|
||||
},
|
||||
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
|
||||
};
|
||||
@ -46,17 +45,19 @@ export default function Player(props) {
|
||||
overlay = imgTag.outerHTML;
|
||||
}
|
||||
|
||||
player.overlay({
|
||||
align: props.logo.position,
|
||||
overlays: [
|
||||
{
|
||||
showBackground: false,
|
||||
content: overlay,
|
||||
start: 'playing',
|
||||
end: 'pause',
|
||||
},
|
||||
],
|
||||
});
|
||||
if (player.overlay) {
|
||||
player.overlay({
|
||||
align: props.logo.position,
|
||||
overlays: [
|
||||
{
|
||||
showBackground: false,
|
||||
content: overlay,
|
||||
start: 'playing',
|
||||
end: 'pause',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (props.autoplay === true) {
|
||||
@ -79,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 {
|
||||
top: -0.4em;
|
||||
top: 0em;
|
||||
right: -0.5em;
|
||||
}
|
||||
|
||||
|
||||
99
src/misc/Player/video-js-skin-internal.min.css
vendored
99
src/misc/Player/video-js-skin-internal.min.css
vendored
@ -1,98 +1 @@
|
||||
.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, 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%;
|
||||
}
|
||||
.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%}
|
||||
@ -1,117 +1,117 @@
|
||||
.vjs-public {
|
||||
--video-js--primary: #eaea05;
|
||||
--video-js--primary: #EAEA05;
|
||||
}
|
||||
|
||||
/* play btn */
|
||||
|
||||
.vjs-public .vjs-big-play-button {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: none;
|
||||
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);
|
||||
}
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: none;
|
||||
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:hover .vjs-big-play-button,
|
||||
.vjs-public.vjs-big-play-button:focus {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.vjs-public:hover .vjs-big-play-button,
|
||||
.vjs-public.vjs-big-play-button:focus {
|
||||
background-color: transparent;
|
||||
color: rgba(255,255,255,1);
|
||||
}
|
||||
|
||||
/* controlbar */
|
||||
|
||||
.vjs-public .vjs-control-bar {
|
||||
height: 70px;
|
||||
padding-top: 20px;
|
||||
background: none;
|
||||
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
||||
height: 70px;
|
||||
padding-top: 20px;
|
||||
background: none;
|
||||
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-button>.vjs-icon-placeholder:before {
|
||||
line-height: 50px
|
||||
}
|
||||
|
||||
/* progressbar */
|
||||
|
||||
.vjs-public .vjs-play-progress:before {
|
||||
display: none;
|
||||
display: none
|
||||
}
|
||||
|
||||
.vjs-public .vjs-progress-control {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 15px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
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;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin: 0
|
||||
}
|
||||
|
||||
.vjs-public .vjs-play-progress {
|
||||
background-color: var(--video-js--primary);
|
||||
background-color: var(--video-js--primary);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-slider {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
background: rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-load-progress {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
background: rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-load-progress div {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
background: rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-remaining-time {
|
||||
order: 0;
|
||||
line-height: 50px;
|
||||
flex: 3;
|
||||
text-align: left;
|
||||
order: 0;
|
||||
line-height: 50px;
|
||||
flex: 3;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-live-control {
|
||||
line-height: 50px;
|
||||
line-height: 50px;
|
||||
}
|
||||
|
||||
/* volume-panel */
|
||||
|
||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||
padding-top: 1em;
|
||||
padding-top: .95em;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-control .vjs-volume-panel {
|
||||
width: 4.5em;
|
||||
width: 4.5em;
|
||||
}
|
||||
|
||||
/* live display */
|
||||
|
||||
.vjs-public .vjs-live-display {
|
||||
margin-left: 1.8em;
|
||||
margin-left: 1.8em;
|
||||
}
|
||||
|
||||
/* disable caps */
|
||||
|
||||
.vjs-internal .vjs-subs-caps-button {
|
||||
display: none;
|
||||
.vjs-public .vjs-subs-caps-button {
|
||||
display: none!important;
|
||||
}
|
||||
|
||||
/* spacer */
|
||||
@ -123,47 +123,52 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.vjs-public .vjs-overlay > a > img {
|
||||
width: 100%;
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 25%!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 28% !important;
|
||||
max-height: 28% !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: 20px !important;
|
||||
left: 30px !important;
|
||||
top: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-right {
|
||||
top: 20px !important;
|
||||
right: 30px !important;
|
||||
top: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-left {
|
||||
bottom: 20px !important;
|
||||
left: 30px !important;
|
||||
bottom: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-right {
|
||||
bottom: 20px !important;
|
||||
right: 30px !important;
|
||||
bottom: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* context menu */
|
||||
|
||||
.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 {
|
||||
background: unset !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
||||
min-width: 100px;
|
||||
background: unset!important;
|
||||
border-bottom: 1px solid rgba(255,255,255,.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-lock-open {
|
||||
z-index: 1000;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
124
src/misc/Player/video-js-skin-public.min.css
vendored
124
src/misc/Player/video-js-skin-public.min.css
vendored
@ -1,123 +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, 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;
|
||||
}
|
||||
.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}
|
||||
@ -58,7 +58,7 @@ export default function VideoJS(props) {
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
spacing={2}
|
||||
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
|
||||
>
|
||||
<div data-vjs-player>
|
||||
|
||||
@ -28,6 +28,8 @@ function init(props) {
|
||||
drop: 0,
|
||||
dup: 0,
|
||||
frames: 0,
|
||||
cpu: 0,
|
||||
memory: 0,
|
||||
...props,
|
||||
};
|
||||
|
||||
@ -57,7 +59,33 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={Math.round(progress.fps)} />
|
||||
<Number value={progress.cpu} digits={2} minDigits={2} />%
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<Trans>CPU</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={progress.memory / 1024 / 1024} digits={0} minDigits={0} /> MB
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<Trans>Memory</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={progress.fps} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
@ -70,7 +98,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={progress.bitrate} minDigits={2} />
|
||||
<Number value={progress.bitrate} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
@ -83,7 +111,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={progress.q} digits={2} />
|
||||
<Number value={progress.q} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
@ -96,7 +124,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={progress.speed} minDigits={2} />
|
||||
<Number value={progress.speed} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
@ -108,7 +136,9 @@ export default function Progress(props) {
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>{!isNaN(Math.round((props.drop * 100) / props.frames)) || 0}%</strong>
|
||||
<strong>
|
||||
<Number value={!isNaN((props.drop * 100) / props.frames) || 0} digits={2} minDigits={2} />%
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<Trans>Frame drops</Trans>
|
||||
@ -120,7 +150,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={Math.round(progress.dup)} />
|
||||
<Number value={progress.dup} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
|
||||
@ -41,7 +41,11 @@ export default function Component(props) {
|
||||
isCustom: v === props.customKey ? true : false,
|
||||
});
|
||||
|
||||
props.onChange(event);
|
||||
props.onChange({
|
||||
target: {
|
||||
value: v === props.customKey ? value.custom : value.value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleCustomChange = (event) => {
|
||||
@ -59,7 +63,7 @@ export default function Component(props) {
|
||||
options.push(
|
||||
<MenuItem key={o.value} value={o.value} disabled={o.disabled === true}>
|
||||
{o.label}
|
||||
</MenuItem>
|
||||
</MenuItem>,
|
||||
);
|
||||
}
|
||||
|
||||
@ -70,7 +74,7 @@ export default function Component(props) {
|
||||
{$value.isCustom === true ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={6}>
|
||||
<FormControl variant={props.variant} fullWidth MenuProps={{ disableScrollLock: true }}>
|
||||
<FormControl variant={props.variant} fullWidth>
|
||||
<InputLabel>{props.label}</InputLabel>
|
||||
<Select
|
||||
value={$value.isCustom === false ? $value.value : props.customKey}
|
||||
|
||||
97
src/misc/UploadButton.js
Normal file
97
src/misc/UploadButton.js
Normal file
@ -0,0 +1,97 @@
|
||||
import React from 'react';
|
||||
|
||||
import FormInlineButton from './FormInlineButton';
|
||||
|
||||
export default function UploadButton(props) {
|
||||
const { acceptTypes, label, onError, onStart, onUpload, ...other } = props;
|
||||
|
||||
const accept = props.acceptTypes.map((t) => t.mimetype);
|
||||
|
||||
const handleUpload = (event) => {
|
||||
const handler = (event) => {
|
||||
const files = event.target.files;
|
||||
|
||||
if (files.length === 0) {
|
||||
// no files selected
|
||||
props.onError({
|
||||
type: 'nofiles',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
|
||||
let type = null;
|
||||
for (let t of props.acceptTypes) {
|
||||
const accept = t.mimetype.split('/');
|
||||
const actual = file.type.split('/');
|
||||
|
||||
if (accept[0] !== actual[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (accept[1] === '*' || accept[1] === actual[1]) {
|
||||
type = t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === null) {
|
||||
// not one of the allowed mimetypes
|
||||
props.onError({
|
||||
type: 'mimetype',
|
||||
actual: file.type,
|
||||
allowed: accept.slice(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.size > type.maxSize) {
|
||||
// the file is too big
|
||||
props.onError({
|
||||
type: 'size',
|
||||
actual: file.size,
|
||||
allowed: type.maxSize,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let reader = new FileReader();
|
||||
reader.readAsArrayBuffer(file);
|
||||
reader.onloadend = async () => {
|
||||
if (reader.result === null) {
|
||||
// reading the file failed
|
||||
props.onError({
|
||||
type: 'read',
|
||||
message: reader.error.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
props.onUpload(reader.result, type.extension, type.mimetype);
|
||||
};
|
||||
};
|
||||
|
||||
props.onStart();
|
||||
|
||||
handler(event);
|
||||
|
||||
// reset the value such that the onChange event will be triggered again
|
||||
// if the same file gets selected again
|
||||
event.target.value = null;
|
||||
};
|
||||
|
||||
return (
|
||||
<FormInlineButton component="label" {...other}>
|
||||
{props.label}
|
||||
<input accept={accept.join(',')} type="file" hidden onChange={handleUpload} />
|
||||
</FormInlineButton>
|
||||
);
|
||||
}
|
||||
|
||||
UploadButton.defaultProps = {
|
||||
label: '',
|
||||
acceptTypes: [],
|
||||
onError: function () {},
|
||||
onUpload: function (data, extension) {},
|
||||
};
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,10 +10,14 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: [],
|
||||
filter: [],
|
||||
};
|
||||
|
||||
return mapping;
|
||||
@ -19,6 +25,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +35,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -41,6 +49,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
@ -50,12 +59,12 @@ const codecs = [];
|
||||
const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user