Compare commits
2 Commits
main
...
chromecast
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e1945d895 | ||
|
|
bb9bfb0ee7 |
@ -10,4 +10,3 @@ node_modules/
|
||||
.github
|
||||
.github_build
|
||||
.build
|
||||
NONPUBLIC/
|
||||
@ -1 +0,0 @@
|
||||
src/locales/*/messages.js
|
||||
129
.github/workflows/build_restreamer-ui.yaml
vendored
@ -1,88 +1,61 @@
|
||||
name: 'Build main restreamer-ui'
|
||||
name: 'Build restreamer-ui'
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore:
|
||||
- '**'
|
||||
|
||||
jobs:
|
||||
build-frontend:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
version: ${{ steps.latestversion.outputs.version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
docker:
|
||||
runs-on: [self-hosted]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Get latest version from package.json
|
||||
id: latestversion
|
||||
run: |
|
||||
echo "version=$(cat ./package.json | jq '.version' | sed 's/\"//g')" >> "$GITHUB_OUTPUT"
|
||||
- uses: cardinalby/export-env-action@v1
|
||||
with:
|
||||
envFile: '.github_build/Build.restreamer-ui.env'
|
||||
export: 'true'
|
||||
expandWithJobEnv: 'true'
|
||||
expand: 'true'
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '21'
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@master
|
||||
with:
|
||||
platforms: all
|
||||
|
||||
- name: Build React App
|
||||
run: |
|
||||
yarn install
|
||||
yarn build
|
||||
env:
|
||||
PUBLIC_URL: './'
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@master
|
||||
|
||||
- name: Upload React build as artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: restreamerui-main-build
|
||||
path: build/
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
build-docker:
|
||||
needs: build-frontend
|
||||
runs-on: [self-hosted]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- 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: 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 }}
|
||||
- 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
|
||||
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
|
||||
|
||||
88
.github/workflows/build_restreamer-ui_dev.yaml
vendored
@ -1,88 +0,0 @@
|
||||
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 }}
|
||||
2
.github_build/Build.restreamer-ui.env
Normal file
@ -0,0 +1,2 @@
|
||||
# RESTREAMER UI
|
||||
RELEASE=1.1.0
|
||||
2
.gitignore
vendored
@ -1,7 +1,6 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/NONPUBLIC
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
@ -13,7 +12,6 @@
|
||||
/build
|
||||
|
||||
# misc
|
||||
NONPUBLIC
|
||||
.DS_Store
|
||||
.VSCodeCounter
|
||||
.env.local
|
||||
|
||||
13
.linguirc
@ -14,19 +14,12 @@
|
||||
"sourceLocale": "en",
|
||||
"locales": [
|
||||
"en",
|
||||
"da",
|
||||
"de",
|
||||
"el",
|
||||
"es",
|
||||
"fr",
|
||||
"it",
|
||||
"ko",
|
||||
"pl",
|
||||
"pt-br",
|
||||
"ru",
|
||||
"sl",
|
||||
"tr",
|
||||
"uk",
|
||||
"zh-hans"
|
||||
"pt",
|
||||
"es",
|
||||
"ru"
|
||||
]
|
||||
}
|
||||
172
CHANGELOG.md
@ -1,170 +1,6 @@
|
||||
# Restreamer-UI
|
||||
|
||||
## v1.13.0 > v1.14.0
|
||||
|
||||
- Add wettercom service
|
||||
- Add option to select which channels will be displayed on the playersite ([#392](https://github.com/datarhei/restreamer/issues/392), [#800](https://github.com/datarhei/restreamer/issues/800))
|
||||
- Mod updates public videojs >v8
|
||||
- Fix erroneous filter setting
|
||||
- Fix encoded address
|
||||
- Fix double -filter parameter when encoder sets filter
|
||||
- Fix Docker build ([#64](https://github.com/datarhei/restreamer-ui/issues/64))
|
||||
|
||||
## v1.12.0 > v1.13.0
|
||||
|
||||
- Add to allow stream hints in case probing fails
|
||||
- Mod enables ff-loglevel and prepares the logging component
|
||||
- Mod uses official Instagram-RTMP target
|
||||
- Mod Remove unused imports
|
||||
- Mod Update translations
|
||||
- Mod updates dep.
|
||||
- Fix player position
|
||||
- Fix missing stream URL, summarize streams in probe log, don't lock type for first stream
|
||||
|
||||
## v1.11.0 > v1.12.0
|
||||
|
||||
- Add option to select different SRT stream in wizard
|
||||
- Add option to select different RTMP stream in wizard
|
||||
- Fix selecting other than first audio stream ([#710](https://github.com/datarhei/restreamer/issues/710))
|
||||
- Fix reset of previous audio settings when editing profile ([#730](https://github.com/datarhei/restreamer/issues/730))
|
||||
- Fix RTMP URL for receive mode
|
||||
|
||||
## v1.10.0 > v1.11.0
|
||||
|
||||
- Add allow to stream HEVC and AV1 to Youtube via RTMP
|
||||
- Add librav1e AV1 encoder
|
||||
- Add support for AV1 CUDA decoding ([PR 46](https://github.com/datarhei/restreamer-ui/pull/46))
|
||||
- Add FFmpeg 6 support
|
||||
- Add HEVC VideoToolbox encoder
|
||||
- Fix anonymize error message ([#688](https://github.com/datarhei/restreamer/issues/688))
|
||||
- Fix chromecast config ([#37](https://github.com/datarhei/restreamer-ui/issues/37))
|
||||
|
||||
## v1.9.0 > v1.10.0
|
||||
|
||||
- Add resource usage and ffmpeg command to process details
|
||||
- Add audio loop source
|
||||
- Add to allow to select from already publishing RTMP and SRT streams
|
||||
- Fix wrongly displayed SRT URL ([#635](https://github.com/datarhei/restreamer/issues/635))
|
||||
- Fix RTMPS address with custom ports ([#658](https://github.com/datarhei/restreamer/issues/658))
|
||||
- Fix allow RTSPS protocol ([#677](https://github.com/datarhei/restreamer/issues/677))
|
||||
|
||||
## v1.8.0 > v1.9.0
|
||||
|
||||
- Add enlarged channel overview
|
||||
- Add new publication services: Dailymotion, Livepush, kick.com, NimoTV, PicartoTV, Rumble
|
||||
- Add frame interpolation (framerate) filter (thanks to orryverducci)
|
||||
- Add -referer option for pulling HTTP streams ([PR 40](https://github.com/datarhei/restreamer-ui/pull/40), thanks to mdastgheib)
|
||||
- Add a/v filter to the publication components ([#593](https://github.com/datarhei/restreamer-ui/issues/593))
|
||||
- Add video or image loop as input ([#528](https://github.com/datarhei/restreamer/discussions/528))
|
||||
- Add option for custom poster image in player ([#632](https://github.com/datarhei/restreamer/issues/632))
|
||||
- Add option to allow to set limits for ingest and egress processes ([#636](https://github.com/datarhei/restreamer/issues/636))
|
||||
- Mod extends twitch's server list
|
||||
- Mod uses placeholders for ingress setups ([#560](https://github.com/datarhei/restreamer-ui/issues/560))
|
||||
- Mod updates npm
|
||||
- Fix Owncast typo
|
||||
- Fix Restream grid
|
||||
- Fix the advanced settings in the MPEG-TS publication service ([#597](https://github.com/datarhei/restreamer/issues/597), thanks to orryverducci)
|
||||
- Fix ALSA demuxer option names
|
||||
- Fix index out-of-range warning, list ALSA devices for Raspicam video source
|
||||
- Fix MUI warning
|
||||
- Fix videojs skin
|
||||
|
||||
## v1.7.0 > v1.8.0
|
||||
|
||||
- Add stream key field and protocol detection to RTMP publication service
|
||||
- Add Chinese (simplified) translation (thanks to Huyg0180110559)
|
||||
- Add Ukrainian translation (thanks to Yurii Denys)
|
||||
- Fix empty force_key_frames value
|
||||
- Fix Icecast publication service
|
||||
- Fix imprint, terms and credit without share ([#525](https://github.com/datarhei/restreamer/issues/529))
|
||||
- Fix proxy error on the playersite ([#525](https://github.com/datarhei/restreamer/issues/525))
|
||||
- Fix saving RTMP advanced options ([#518](https://github.com/datarhei/restreamer/issues/518))
|
||||
- Fix help buttons for other languages than English and German ([#24](https://github.com/datarhei/restreamer-ui/issues/24))
|
||||
- Fix internal player skin (volume bar)
|
||||
- Fix security hints (npm dep.)
|
||||
|
||||
## v1.6.0 > v1.7.0
|
||||
|
||||
- Add analyzeduration, probesize and max_probe_packets input options
|
||||
- Add avoid_negative_ts input option
|
||||
- Add http_proxy input option ([#513](https://github.com/datarhei/restreamer/issues/513))
|
||||
- Add copyts, start_at_zero and use_wallclock_as_timestamps input options
|
||||
- Add heuristic to find core address if UI is proxied
|
||||
- Add Turkish translation (thanks to Ramazan Sancar) ([#22](https://github.com/datarhei/restreamer-ui/issues/22))
|
||||
- Add Danish translation (Thanks to Filip Stadler and Info)
|
||||
- Add Slovenian translation (thanks to Grega)
|
||||
- Add Greek translation
|
||||
- Mod allows general input settings for pull and push streams
|
||||
- Mod updates npm dependencies
|
||||
- Fix Creative Commons icons
|
||||
- Fix positioning of the deinterlacing filter ([#465](https://github.com/datarhei/restreamer/issues/465))
|
||||
|
||||
## v1.5.1 > v1.6.0
|
||||
|
||||
- Add Bob Weaver Deinterlacing Filter ([#465](https://github.com/datarhei/restreamer/issues/465))
|
||||
- Add tests for wizard, network source, and coders
|
||||
- Add Korean translation (thanks to Jihaeng)
|
||||
- Mod splitting wizard in components
|
||||
- Fix wrong call to encoder defaults ([#467](https://github.com/datarhei/restreamer/issues/467))
|
||||
|
||||
## v1.5.0 > v1.5.1
|
||||
|
||||
- Fix FFmpeg version check for RTSP sources ([#455](https://github.com/datarhei/restreamer/issues/455))
|
||||
- Fix requires Core >= v16.11.0 and FFmpeg >= 5.1.0
|
||||
|
||||
## v1.4.0 > v1.5.0
|
||||
|
||||
- Add changelog viewer
|
||||
- Add skills props to encoder and decoder components
|
||||
- Add fps_mode to x264, x265, vp9 encoder
|
||||
- Add scale filter to non-hwaccel encoders
|
||||
- Add PeerTube and Media Network to publication services (plattforms, software)
|
||||
- Add reset button to hide a player logo ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||
- Mod expands V4L2_M2M options (an unstable RPI 64bit encoder)
|
||||
- Mod indicates a faulty cache configuration
|
||||
- Mod switches to the improved SRT syntax (thx to SA Consulting)
|
||||
- Mod improves display of progress data
|
||||
- Mod removes deprecated param ocl - now ochl (ff5)
|
||||
- Mod simplifies the setup of Restreamer-to-Restreamer connections
|
||||
- Mod adds Istafeed.me as StreamKey service to Instagram's publishing service
|
||||
- Mod renames "Low delay" to "Low latency (buffer)" and set false as default (requires more feedback)
|
||||
- Del removes support for clappr player
|
||||
- Fix npm dependencies (security fixes)
|
||||
- Fix videojs-overlay logo size ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||
- Fix use of TLS for input from local RTMP server
|
||||
- Fix Icecast publication service settings (datarhei/restreamer#429)
|
||||
- Fix removes SRT bitstream on tee (OBS > RTMP > SRT is faulty)
|
||||
|
||||
Dependency:
|
||||
|
||||
- datarhei Core v16.11.0+
|
||||
|
||||
## v1.3.0 > v1.4.0
|
||||
|
||||
- Add email field for Let's Encrypt certification
|
||||
|
||||
Dependency:
|
||||
|
||||
- datarhei Core v16.10.1+
|
||||
|
||||
## v1.2.0 > v1.3.0
|
||||
|
||||
- Add dlive & Trovo publication services
|
||||
- Add low_delay option to processing (default: true)
|
||||
- Mod uses the ingest stream for publication (datarhei/restreamer#411)
|
||||
- Mod optimized DVR on DiskFS
|
||||
- Mod updates packages
|
||||
- Fix SRT bitstream on tee
|
||||
- Fix typo
|
||||
- Fix viewer count (datarhei/restreamer#394)
|
||||
- Fix user registration if username and/or password are set via environment (datarhei/restreamer-ui#13)
|
||||
- Fix Dockerfile, Reduce size, serve production build (datarhei/restreamer-ui#12)
|
||||
|
||||
Dependency:
|
||||
|
||||
- datarhei Core v16.10.0+
|
||||
|
||||
## v1.1.0 > v1.2.0
|
||||
#### v1.1.0 > v1.2.0
|
||||
|
||||
- Add allow writing HLS to disk
|
||||
- Add audio pan filter
|
||||
@ -174,7 +10,7 @@ Dependency:
|
||||
- Add audio loudness normalization filter
|
||||
- Add audio resample filter, that was before part of the encoders
|
||||
- Add HLS Master playlist (requires FFmpeg hlsbitrate.patch) (thx Dwaynarang, Electra Player compatibility)
|
||||
- Add LinkedIn & Azure Media Services to publication services (thx kalashnikov)
|
||||
- Add linkedIn & Azure Media Services to publication services (thx kalashnikov)
|
||||
- Add AirPlay support with silvermine videojs plugin
|
||||
- Add Chromecast support (thx badincite, [#10](https://github.com/datarhei/restreamer-ui/pull/10))
|
||||
- Add stream distribution across multiple internal servers
|
||||
@ -185,8 +21,8 @@ Dependency:
|
||||
- Add Polish translations (thx Robert Rykała)
|
||||
- Mod extends the datarhei Core publication service with srt streaming
|
||||
- Mod allow decoders and encoders to set global options
|
||||
- Mod allow trailing slash on Core address
|
||||
- Fix player problem with different stream formats (9:16)
|
||||
- Mod allow trailing slash on Core address
|
||||
- Fix process report naming
|
||||
- Fix publication service icon styles
|
||||
- Fix VAAPI encoder
|
||||
@ -195,7 +31,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))
|
||||
|
||||
26
Dockerfile
@ -1,25 +1,23 @@
|
||||
ARG NODE_IMAGE=node:21-alpine3.20
|
||||
ARG CADDY_IMAGE=caddy:2.8.4-alpine
|
||||
FROM node:17.9.0-alpine3.15
|
||||
|
||||
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="./"
|
||||
ARG PUBLIC_URL "/"
|
||||
|
||||
COPY . /ui
|
||||
|
||||
WORKDIR /ui
|
||||
|
||||
RUN cd /ui && \
|
||||
yarn install && \
|
||||
yarn build
|
||||
|
||||
FROM $CADDY_IMAGE
|
||||
|
||||
COPY --from=builder /ui/build /ui/build
|
||||
COPY --from=builder /ui/Caddyfile /ui/Caddyfile
|
||||
|
||||
WORKDIR /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
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]
|
||||
CMD [ "npm", "run", "start" ]
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
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
@ -26,5 +26,23 @@ $ 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.
|
||||
|
||||
107
package.json
@ -1,61 +1,57 @@
|
||||
{
|
||||
"name": "restreamer-ui",
|
||||
"version": "1.14.0",
|
||||
"bundle": "restreamer-v2.12.0",
|
||||
"version": "1.2.0",
|
||||
"bundle": "restreamer-v2.x.x",
|
||||
"private": false,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@auth0/auth0-spa-js": "^1.22.1",
|
||||
"@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.9.3",
|
||||
"@emotion/styled": "^11.9.3",
|
||||
"@fontsource/dosis": "^4.5.8",
|
||||
"@fontsource/roboto": "^4.5.7",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||
"@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.8.4",
|
||||
"@mui/lab": "^5.0.0-alpha.90",
|
||||
"@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",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"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.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",
|
||||
"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",
|
||||
"react-colorful": "^5.5.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",
|
||||
"url-parse": "^1.5.10",
|
||||
"util": "^0.12.5",
|
||||
"uuid": "^10.0.0",
|
||||
"video.js": "^8.17.3",
|
||||
"videojs-overlay": "^3.1.0"
|
||||
"uuid": "^8.3.2",
|
||||
"video.js": "^7.19.2",
|
||||
"videojs-overlay": "^2.1.5"
|
||||
},
|
||||
"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",
|
||||
@ -77,7 +73,7 @@
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11"
|
||||
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11, maintained node versions"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
@ -86,13 +82,14 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@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",
|
||||
"@babel/core": "^7.18.6",
|
||||
"@lingui/cli": "^3.14.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"prettier": "^2.7.1",
|
||||
"react-error-overlay": "^6.0.11"
|
||||
},
|
||||
"resolutions": {}
|
||||
}
|
||||
"resolutions": {
|
||||
"url-parse@1.5.3": "patch:url-parse@npm:1.5.3#.yarn/patches/url-parse-npm-1.5.3-225ab9cae7.patch",
|
||||
"react-error-overlay": "6.0.9"
|
||||
}
|
||||
}
|
||||
|
||||
5
public/_player/clappr/dist/clappr-nerd-stats.min.js
vendored
Normal file
1
public/_player/clappr/dist/clappr-stats.min.js
vendored
Normal file
85
public/_player/clappr/dist/clappr.min.js
vendored
Normal file
1
public/_player/clappr/dist/clappr.min.js.map
vendored
Normal file
4
public/_player/clappr/files.txt
Normal file
@ -0,0 +1,4 @@
|
||||
dist/clappr.min.js.map
|
||||
dist/clappr.min.js
|
||||
dist/clappr-stats.min.js
|
||||
dist/clappr-nerd-stats.min.js
|
||||
112
public/_player/clappr/player.html
Normal file
@ -0,0 +1,112 @@
|
||||
<!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>
|
||||
|
Before Width: | Height: | Size: 623 B After Width: | Height: | Size: 623 B |
1
public/_player/videojs/dist/ic_cast_connected_white.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" baseProfile="basic" height="24" viewBox="0 0 24 24" width="24"><g fill="#fff"><path d="M768 481.938h-36c-2.2 0-4 1.8-4 4V492h3.984v-6.016H768v27.975h-14.041v3.979H768c2.2 0 4-1.8 4-4v-28c0-2.201-1.8-4-4-4z"/><path d="M728.104 495.938c-.035 0-.069.002-.104.002v4.502c.035 0 .069-.005.104-.005 9.67 0 17.499 7.83 17.499 17.5h4.5c.001-12.157-9.841-21.999-21.999-21.999z"/><path d="M728.104 503.938c-.035 0-.069.005-.104.005v4.5c.035 0 .069-.005.104-.005 5.248 0 9.5 4.252 9.5 9.5h4.5c0-7.736-6.264-14-14-14zm-.104 14h6c0-3.256-2.64-5.896-5.896-5.896-.035 0-.069.005-.104.005v5.891zm8-27.626v2.885c7.934 2.563 14.196 8.832 16.75 16.77.043.096.09.188.132.283H764v-19.938h-28z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 745 B |
|
Before Width: | Height: | Size: 981 B |
1
public/_player/videojs/dist/ic_cast_white.svg
vendored
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" baseProfile="basic" height="24" viewBox="0 0 24 24" width="24"><g fill="#fff"><path d="M768 481.938h-36c-2.2 0-4 1.8-4 4V492h3.984v-6.016H768v27.975h-14.041v3.979H768c2.2 0 4-1.8 4-4v-28c0-2.201-1.8-4-4-4z"/><path d="M728.104 495.938c-.035 0-.069.002-.104.002v4.502c.035 0 .069-.005.104-.005 9.67 0 17.499 7.83 17.499 17.5h4.5c.001-12.157-9.841-21.999-21.999-21.999z"/><path d="M728.104 503.938c-.035 0-.069.005-.104.005v4.5c.035 0 .069-.005.104-.005 5.248 0 9.5 4.252 9.5 9.5h4.5c0-7.736-6.264-14-14-14zm-.104 14h6c0-3.256-2.64-5.896-5.896-5.896-.035 0-.069.005-.104.005v5.891z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 653 B |
BIN
public/_player/videojs/dist/ic_cast_white_24dp.png
vendored
|
Before Width: | Height: | Size: 824 B |
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: .9em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-control .vjs-volume-panel {
|
||||
@ -110,8 +110,8 @@
|
||||
|
||||
/* disable caps */
|
||||
|
||||
.vjs-public .vjs-subs-caps-button {
|
||||
display: none!important;
|
||||
.vjs-internal .vjs-subs-caps-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* spacer */
|
||||
@ -123,37 +123,33 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 25%!important;
|
||||
.vjs-public .vjs-overlay > a > img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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-no-background {
|
||||
max-width: 28%!important;
|
||||
max-height: 28%!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-left {
|
||||
top: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: right;
|
||||
top: 20px!important;
|
||||
left: 30px!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-right {
|
||||
top: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: right;
|
||||
top: 20px!important;
|
||||
right: 30px!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-left {
|
||||
bottom: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: left;
|
||||
bottom: 20px!important;
|
||||
left: 30px!important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-right {
|
||||
bottom: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: left;
|
||||
bottom: 20px!important;
|
||||
right: 30px!important;
|
||||
}
|
||||
|
||||
/* context menu */
|
||||
@ -170,3 +166,4 @@
|
||||
.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: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}
|
||||
.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}
|
||||
792
public/_player/videojs/dist/video-js.css
vendored
2
public/_player/videojs/dist/video-js.min.css
vendored
47035
public/_player/videojs/dist/video.js
vendored
56
public/_player/videojs/dist/video.min.js
vendored
@ -1 +0,0 @@
|
||||
.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
@ -1,308 +0,0 @@
|
||||
(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: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}
|
||||
.vjs-airplay-button .vjs-icon-placeholder{background:url(ic_airplay_white.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.svg)}
|
||||
|
||||
1317
public/_player/videojs/dist/videojs-airplay.min.js
vendored
@ -1 +1,65 @@
|
||||
.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}
|
||||
/** Silvermine Chromecast **/
|
||||
.vjs-chromecast-button .vjs-icon-placeholder {
|
||||
background: url('ic_cast_white.svg') 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: url('ic_cast_white.svg');
|
||||
}
|
||||
.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder {
|
||||
background: url('ic_cast_connected_white.svg');
|
||||
}
|
||||
.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder {
|
||||
background: url('ic_cast_connected_white.svg');
|
||||
}
|
||||
.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;
|
||||
}
|
||||
|
||||
4100
public/_player/videojs/dist/videojs-chromecast.js
vendored
@ -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: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}
|
||||
.vjs-chromecast-button .vjs-icon-placeholder{background:url(ic_cast_white.svg) 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:url(ic_cast_white.svg)}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background:url(ic_cast_connected_white.svg)}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background:url(ic_cast_connected_white.svg)}.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
737
public/_player/videojs/dist/videojs-license.js
vendored
@ -1,340 +1,459 @@
|
||||
/*! @name videojs-license @version 0.1.0 @license MIT */
|
||||
(function (global, factory) {
|
||||
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';
|
||||
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 }; }
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
|
||||
var document__default = /*#__PURE__*/_interopDefaultLegacy(document);
|
||||
|
||||
var version = "0.1.0";
|
||||
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;
|
||||
}
|
||||
|
||||
const Plugin = videojs__default["default"].getPlugin('plugin');
|
||||
const Component = videojs__default["default"].getComponent('Component');
|
||||
const Button = videojs__default["default"].getComponent('MenuButton');
|
||||
function commonjsRequire () {
|
||||
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
|
||||
}
|
||||
|
||||
// Default options for the plugin.
|
||||
const defaults = {
|
||||
license: 'none',
|
||||
title: '',
|
||||
author: '',
|
||||
languages: {
|
||||
license: 'License',
|
||||
loading: 'Loading'
|
||||
}
|
||||
};
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
module.exports = _assertThisInitialized;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
// 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();
|
||||
}
|
||||
});
|
||||
}
|
||||
var setPrototypeOf = createCommonjsModule(function (module) {
|
||||
function _setPrototypeOf(o, p) {
|
||||
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
|
||||
o.__proto__ = p;
|
||||
return o;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the menu ui button to the controlbar
|
||||
*/
|
||||
buildUI() {
|
||||
const playerId = this.playerId;
|
||||
const that = this;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
return _setPrototypeOf(o, p);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
module.exports = _setPrototypeOf;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
}
|
||||
var inheritsLoose = createCommonjsModule(function (module) {
|
||||
function _inheritsLoose(subClass, superClass) {
|
||||
subClass.prototype = Object.create(superClass.prototype);
|
||||
subClass.prototype.constructor = subClass;
|
||||
setPrototypeOf(subClass, superClass);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This is just build the top level menu no sub menus
|
||||
*/
|
||||
buildTopLevelMenu() {
|
||||
const settingsButton = this.player.getChild('controlBar').getChild('licenseMenuButton');
|
||||
module.exports = _inheritsLoose;
|
||||
module.exports["default"] = module.exports, module.exports.__esModule = true;
|
||||
});
|
||||
|
||||
// settingsButton.addClass('vjs-license-is-loaded');
|
||||
var version = "0.1.0";
|
||||
|
||||
const main = settingsButton.menu.contentEl_;
|
||||
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.
|
||||
|
||||
// Empty the main menu div to repopulate
|
||||
main.innerHTML = '';
|
||||
main.classList.add('vjs-license-top-level');
|
||||
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/
|
||||
*/
|
||||
|
||||
// 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);
|
||||
}
|
||||
var License = /*#__PURE__*/function (_Plugin) {
|
||||
inheritsLoose(License, _Plugin);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
// 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (options.license === 'none') {
|
||||
return assertThisInitialized(_this);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
_this.player.ready(function () {
|
||||
_this.player.addClass('vjs-license');
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
_this.buildUI();
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
|
||||
_this.mobileBuildUI();
|
||||
}
|
||||
}); // close the menu if open on userinactive
|
||||
|
||||
/**
|
||||
* 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
_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
|
||||
|
||||
// Define default values for the plugin's `state` object here.
|
||||
License.defaultState = {};
|
||||
|
||||
// Include the version number.
|
||||
License.VERSION = version;
|
||||
_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');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Register the plugin with video.js.
|
||||
videojs__default["default"].registerPlugin('license', License);
|
||||
_this.player.on('loadstart', function (_event) {
|
||||
_this.removeElementsByClass('vjs-license-clear');
|
||||
|
||||
return License;
|
||||
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;
|
||||
|
||||
})));
|
||||
|
||||
@ -7,8 +7,8 @@ dist/videojs-license.min.js
|
||||
dist/videojs-license.min.css
|
||||
dist/videojs-chromecast.min.js
|
||||
dist/videojs-chromecast.min.css
|
||||
dist/ic_cast_connected_white_24dp.png
|
||||
dist/ic_cast_white_24dp.png
|
||||
dist/ic_cast_connected_white.svg
|
||||
dist/ic_cast_white.svg
|
||||
dist/videojs-airplay.min.js
|
||||
dist/videojs-airplay.min.css
|
||||
dist/ic_airplay_white_24px.svg
|
||||
dist/ic_airplay_white.svg
|
||||
|
||||
@ -82,14 +82,15 @@
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
plugins: {
|
||||
license: playerConfig.license
|
||||
}
|
||||
};
|
||||
|
||||
if (playerConfig.chromecast) {
|
||||
config.techOrder = ["chromecast", "html5"];
|
||||
config.plugins.chromecast = {
|
||||
receiverApplicationId: 'CC1AD845'
|
||||
};
|
||||
config.plugins.chromecast = {};
|
||||
}
|
||||
|
||||
if (playerConfig.airplay) {
|
||||
@ -129,8 +130,6 @@
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
player.license(playerConfig.license);
|
||||
}
|
||||
|
||||
if (autoplay === true) {
|
||||
|
||||
67
public/_playersite/clappr.js
Normal file
@ -0,0 +1,67 @@
|
||||
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,7 +619,6 @@
|
||||
{{/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");
|
||||
@ -629,7 +628,6 @@
|
||||
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");
|
||||
@ -666,9 +664,7 @@
|
||||
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}}
|
||||
@ -680,29 +676,20 @@
|
||||
{{/if}}{{/if}}
|
||||
}
|
||||
window.onclick = function(event) {
|
||||
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;
|
||||
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}}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@ -711,10 +698,10 @@
|
||||
var license = '{{channel_license}}';
|
||||
var license_url = '';
|
||||
var license_name = 'unknown';
|
||||
if (license === 'CC0 1.0') {
|
||||
if (license === 'CC0 4.0') {
|
||||
license_name = 'CC0 1.0 Universal'
|
||||
license_url = 'https://creativecommons.org/publicdomain/zero/1.0/';
|
||||
license_image = 'https://mirrors.creativecommons.org/presskit/buttons/88x31/png/pd.png';
|
||||
license_image = 'https://creativecommons.org/publicdomain/zero/1.0/';
|
||||
} else if (license === 'CC BY 4.0') {
|
||||
license_name = 'CC BY 4.0'
|
||||
license_url = 'https://creativecommons.org/licenses/by/4.0/';
|
||||
@ -738,7 +725,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://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-nd.png';
|
||||
license_image = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
|
||||
}
|
||||
document.getElementById("license").setAttribute("href", license_url);
|
||||
document.getElementById("license_image").src = license_image;
|
||||
|
||||
@ -6,32 +6,25 @@ var config = {
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
// Needed to append the url origin in order for the source to properly pass to the cast device
|
||||
// Needed to append the url orgin in order for the source to properly pass to the cast device
|
||||
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
|
||||
plugins: {},
|
||||
plugins: {
|
||||
license: playerConfig.license,
|
||||
},
|
||||
};
|
||||
|
||||
if (chromecast) {
|
||||
config.techOrder = ['chromecast', 'html5'];
|
||||
// Provide a default reciever application ID
|
||||
config.plugins.chromecast = {
|
||||
receiverApplicationId: 'CC1AD845',
|
||||
};
|
||||
config.plugins.chromecast = {};
|
||||
}
|
||||
|
||||
if (airplay) {
|
||||
config.plugins.airPlay = {};
|
||||
}
|
||||
|
||||
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="#282728" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<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": "#282728",
|
||||
"background_color": "#282728"
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
|
||||
54
src/I18n.js
@ -2,63 +2,45 @@ 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-br/messages.js';
|
||||
import { messages as PT } from './locales/pt/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-br': PT,
|
||||
pt: PT,
|
||||
ru: RU,
|
||||
sl: SL,
|
||||
tr: TR,
|
||||
uk: UK,
|
||||
'zh-hans': ZH,
|
||||
});
|
||||
|
||||
const aliases = {
|
||||
pt: 'pt-br',
|
||||
'zh-cn': 'zh-hans',
|
||||
};
|
||||
|
||||
const getAlias = (lang) => {
|
||||
if (lang in aliases) {
|
||||
return aliases[lang];
|
||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
let lang = Storage.Get('language');
|
||||
if (supportedLanguages.indexOf(lang) === -1) {
|
||||
lang = getBrowserLanguage(defaultLanguage);
|
||||
}
|
||||
|
||||
return lang;
|
||||
};
|
||||
|
||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
let lang = getAlias(Storage.Get('language'));
|
||||
if (supportedLanguages.indexOf(lang) === -1) {
|
||||
lang = getAlias(getBrowserLanguage(defaultLanguage));
|
||||
|
||||
if (supportedLanguages.indexOf(lang) === -1) {
|
||||
lang = defaultLanguage;
|
||||
}
|
||||
lang = defaultLanguage;
|
||||
}
|
||||
|
||||
Storage.Set('language', lang);
|
||||
@ -69,7 +51,7 @@ const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
const getBrowserLanguage = (defaultLanguage) => {
|
||||
let lang = window.navigator.language;
|
||||
|
||||
const match = lang.match(/^[a-z]+(-[a-z]+)?/i);
|
||||
const match = lang.match(/^[a-z]+/);
|
||||
if (!match) {
|
||||
return defaultLanguage;
|
||||
}
|
||||
@ -77,7 +59,7 @@ const getBrowserLanguage = (defaultLanguage) => {
|
||||
return match[0].toLowerCase();
|
||||
};
|
||||
|
||||
i18n.activate(getLanguage('en', ['en', 'da', 'de', 'el', 'es', 'fr', 'it', 'ko', 'pl', 'pt-br', 'ru', 'sl', 'tr', 'uk', 'zh-hans']));
|
||||
i18n.activate(getLanguage('en', ['en', 'de', 'es', 'fr', 'it', 'pl', 'pt', 'ru']));
|
||||
|
||||
export default function Provider(props) {
|
||||
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
||||
|
||||
@ -14,15 +14,9 @@ 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: {
|
||||
@ -64,12 +58,6 @@ 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);
|
||||
|
||||
@ -143,8 +131,6 @@ export default function RestreamerUI(props) {
|
||||
|
||||
const valid = await restreamer.current.Validate();
|
||||
|
||||
await checkChangelog();
|
||||
|
||||
setState({
|
||||
...$state,
|
||||
initialized: true,
|
||||
@ -160,93 +146,8 @@ 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,
|
||||
@ -295,25 +196,16 @@ export default function RestreamerUI(props) {
|
||||
});
|
||||
};
|
||||
|
||||
const handlePasswordReset = async (username, loginUsername, password, loginPassword) => {
|
||||
const data = {
|
||||
const handlePasswordReset = async (username, password) => {
|
||||
const [, err] = await restreamer.current.ConfigSet({
|
||||
api: {
|
||||
auth: {
|
||||
enable: true,
|
||||
username: username,
|
||||
password: password,
|
||||
},
|
||||
},
|
||||
version: 3,
|
||||
};
|
||||
|
||||
if (username.length !== 0) {
|
||||
data.api.auth.username = username;
|
||||
}
|
||||
|
||||
if (password.length !== 0) {
|
||||
data.api.auth.password = password;
|
||||
}
|
||||
|
||||
const [, err] = await restreamer.current.ConfigSet(data);
|
||||
});
|
||||
if (err !== null) {
|
||||
notify('error', 'save:settings', `There was an error resetting the password.`);
|
||||
return 'ERROR';
|
||||
@ -357,7 +249,7 @@ export default function RestreamerUI(props) {
|
||||
if (restarted === true) {
|
||||
// After the restart the API requires a login and this means the restart happened
|
||||
await restreamer.current.Validate();
|
||||
await restreamer.current.Login(loginUsername, loginPassword);
|
||||
await restreamer.current.Login(username, password);
|
||||
|
||||
window.location.reload();
|
||||
} else {
|
||||
@ -456,42 +348,30 @@ export default function RestreamerUI(props) {
|
||||
return null;
|
||||
};
|
||||
|
||||
let view = null;
|
||||
if ($state.initialized === false) {
|
||||
view = <Views.Initializing />;
|
||||
} else {
|
||||
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;
|
||||
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} />;
|
||||
} else {
|
||||
view = <Router restreamer={restreamer.current} />;
|
||||
resources = handleResources;
|
||||
}
|
||||
|
||||
const expand = $state.connected && $state.compatibility.compatible && !$state.password;
|
||||
@ -548,9 +428,6 @@ 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 as DOMRouter } from 'react-router-dom';
|
||||
import { Route, Navigate, Routes, HashRouter } from 'react-router-dom';
|
||||
|
||||
import Views from './views';
|
||||
|
||||
@ -11,9 +11,9 @@ export default function Router(props) {
|
||||
const channelid = props.restreamer.GetCurrentChannelID();
|
||||
|
||||
return (
|
||||
<DOMRouter>
|
||||
<HashRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Views.ChannelSelect channelid={channelid} />} />
|
||||
<Route path="/" element={<Views.ChannelSelect restreamer={props.restreamer} />} />
|
||||
<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>
|
||||
</DOMRouter>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
10
src/index.js
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
||||
import '@fontsource/dosis';
|
||||
@ -10,20 +10,18 @@ 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');
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')).render(
|
||||
ReactDOM.render(
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<CssBaseline />
|
||||
<RestreamerUI address={address} />
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
</StyledEngineProvider>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
|
||||
1
src/locales/pt/messages.js
Normal file
@ -47,8 +47,8 @@ export default function Component(props) {
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
justifyContent={props.justifyContent}
|
||||
alignItems={props.alignItems}
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
textAlign={props.textAlign}
|
||||
spacing={1}
|
||||
className={
|
||||
@ -64,6 +64,4 @@ export default function Component(props) {
|
||||
Component.defaultProps = {
|
||||
color: 'light',
|
||||
textAlign: 'left',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
};
|
||||
|
||||
@ -1,197 +0,0 @@
|
||||
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,8 +10,6 @@ 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';
|
||||
@ -121,7 +119,7 @@ const ImageBackdrop = styled('span')(({ theme }) => ({
|
||||
border: `2px solid ${theme.palette.primary.dark}`,
|
||||
}));
|
||||
|
||||
function ChannelButton(props, largeChannelList) {
|
||||
function ChannelButton(props) {
|
||||
const classes = useStyles();
|
||||
const theme = useTheme();
|
||||
|
||||
@ -156,7 +154,7 @@ function ChannelButton(props, largeChannelList) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid item xs={12} sm={6} md={4} lg={3} style={{ paddingBottom: largeChannelList ? '10px' : 'auto' }}>
|
||||
<Grid item xs={12} sm={6} md={4} lg={3}>
|
||||
<ImageButton focusRipple disabled={props.disabled} onClick={props.onClick} style={{ width: props.width }}>
|
||||
<Stack direction="column" spacing={0.5}>
|
||||
<Image
|
||||
@ -194,21 +192,6 @@ 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();
|
||||
@ -222,12 +205,8 @@ 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, onClose, onState } = props;
|
||||
|
||||
const [$largeChannelList, setLargeChannelList] = React.useState(false);
|
||||
const { channels: allChannels, channelid, onClick, onState } = props;
|
||||
|
||||
React.useEffect(() => {
|
||||
(async () => {
|
||||
@ -267,40 +246,19 @@ 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,
|
||||
@ -324,11 +282,6 @@ export default function ChannelList(props) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleLargeChannelList = () => {
|
||||
setLargeChannelList(!$largeChannelList);
|
||||
setPos(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<SwipeableDrawer
|
||||
@ -336,13 +289,7 @@ export default function ChannelList(props) {
|
||||
open={props.open}
|
||||
onOpen={() => {}}
|
||||
onClose={props.onClose}
|
||||
sx={{
|
||||
marginButtom: 60,
|
||||
'& .MuiDrawer-paper': {
|
||||
top: $largeChannelList ? '0px!important' : 'auto!important',
|
||||
height: $largeChannelList ? '100vh' : 'auto',
|
||||
},
|
||||
}}
|
||||
sx={{ marginButtom: 60 }}
|
||||
BackdropProps={{ invisible: true }}
|
||||
classes={{
|
||||
paper: classes.drawerPaper,
|
||||
@ -360,9 +307,6 @@ 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>
|
||||
@ -383,7 +327,7 @@ export default function ChannelList(props) {
|
||||
</IconButton>
|
||||
</Stack>
|
||||
<Stack direction="row" spacing={0} className={classes.channelItems}>
|
||||
<Grid container spacing={0} justifyContent={$largeChannelList ? 'flex-start' : 'center'}>
|
||||
<Grid container spacing={0} justifyContent="center">
|
||||
{$channels}
|
||||
</Grid>
|
||||
</Stack>
|
||||
|
||||
@ -16,19 +16,9 @@ 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
|
||||
@ -40,7 +30,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
const defaults = c.defaults();
|
||||
decoder.settings = defaults.settings;
|
||||
decoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -59,7 +49,6 @@ 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
|
||||
@ -71,7 +60,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
const defaults = c.defaults({});
|
||||
encoder.settings = defaults.settings;
|
||||
encoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -127,10 +116,10 @@ export default function EncodingSelect(props) {
|
||||
let encoderSettingsHelp = null;
|
||||
|
||||
let coder = encoderRegistry.Get(profile.encoder.coder);
|
||||
if (coder !== null && availableEncoders.includes(coder.coder)) {
|
||||
if (coder !== null && props.availableEncoders.includes(coder.coder)) {
|
||||
const Settings = coder.component;
|
||||
|
||||
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} skills={props.skills} onChange={handleEncoderSettingsChange} />;
|
||||
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} onChange={handleEncoderSettingsChange} />;
|
||||
|
||||
if (props.type === 'video' && !['copy', 'none', 'rawvideo'].includes(coder.coder)) {
|
||||
encoderSettingsHelp = handleEncoderHelp(coder.coder);
|
||||
@ -141,7 +130,7 @@ export default function EncodingSelect(props) {
|
||||
|
||||
for (let c of encoderRegistry.List()) {
|
||||
// Does ffmpeg support the coder?
|
||||
if (!availableEncoders.includes(c.coder)) {
|
||||
if (!props.availableEncoders.includes(c.coder)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -155,14 +144,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>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -184,24 +173,22 @@ export default function EncodingSelect(props) {
|
||||
|
||||
if (coder.coder !== 'copy' && coder.coder !== 'none') {
|
||||
let c = decoderRegistry.Get(profile.decoder.coder);
|
||||
if (c !== null && availableDecoders.includes(c.coder)) {
|
||||
if (c !== null && props.availableDecoders.includes(c.coder)) {
|
||||
const Settings = c.component;
|
||||
|
||||
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} skills={props.skills} onChange={handleDecoderSettingsChange} />;
|
||||
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} onChange={handleDecoderSettingsChange} />;
|
||||
}
|
||||
|
||||
// List all decoders for the codec of the stream
|
||||
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, availableDecoders, 'any')) {
|
||||
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, props.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}>
|
||||
@ -250,6 +237,7 @@ EncodingSelect.defaultProps = {
|
||||
streams: [],
|
||||
profile: {},
|
||||
codecs: [],
|
||||
skills: {},
|
||||
availableEncoders: [],
|
||||
availableDecoders: [],
|
||||
onChange: function (encoder, decoder, automatic) {},
|
||||
};
|
||||
|
||||
@ -1,28 +0,0 @@
|
||||
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,21 +42,14 @@ 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="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="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="pl">Polski</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>
|
||||
<MenuItem value="pt">Português </MenuItem>
|
||||
<MenuItem value="ru">Русский </MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 8.9 KiB |
@ -15,9 +15,16 @@ const MenuProps = {
|
||||
|
||||
export default function Component(props) {
|
||||
return (
|
||||
<FormControl variant={props.variant} disabled={props.disabled} fullWidth>
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<InputLabel>{props.label}</InputLabel>
|
||||
<Select multiple value={props.value} onChange={props.onChange} input={<OutlinedInput />} renderValue={props.renderValue} MenuProps={MenuProps}>
|
||||
<Select
|
||||
multiple
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
input={<OutlinedInput />}
|
||||
renderValue={(selected) => selected.join(', ')}
|
||||
MenuProps={MenuProps}
|
||||
>
|
||||
{props.children}
|
||||
</Select>
|
||||
</FormControl>
|
||||
@ -25,10 +32,7 @@ export default function Component(props) {
|
||||
}
|
||||
|
||||
Component.defaultProps = {
|
||||
variant: 'outlined',
|
||||
label: '',
|
||||
value: [],
|
||||
disabled: false,
|
||||
renderValue: (selected) => selected.join(', '),
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
101
src/misc/Player/clappr.js
Normal file
@ -0,0 +1,101 @@
|
||||
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,9 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import Clappr from './clappr';
|
||||
import VideoJS from './videojs';
|
||||
|
||||
export default function Player(props) {
|
||||
const type = props.type ? props.type : 'videojs-internal';
|
||||
const type = props.type ? props.type : 'clappr';
|
||||
|
||||
if (type === 'videojs-internal' || type === 'videojs-public') {
|
||||
const config = {
|
||||
@ -14,9 +15,6 @@ export default function Player(props) {
|
||||
liveui: true,
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
plugins: {
|
||||
reloadSourceOnError: {},
|
||||
},
|
||||
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
|
||||
};
|
||||
|
||||
@ -45,19 +43,17 @@ export default function Player(props) {
|
||||
overlay = imgTag.outerHTML;
|
||||
}
|
||||
|
||||
if (player.overlay) {
|
||||
player.overlay({
|
||||
align: props.logo.position,
|
||||
overlays: [
|
||||
{
|
||||
showBackground: false,
|
||||
content: overlay,
|
||||
start: 'playing',
|
||||
end: 'pause',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
player.overlay({
|
||||
align: props.logo.position,
|
||||
overlays: [
|
||||
{
|
||||
showBackground: false,
|
||||
content: overlay,
|
||||
start: 'playing',
|
||||
end: 'pause',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
if (props.autoplay === true) {
|
||||
@ -80,6 +76,57 @@ 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: 0em;
|
||||
top: -0.4em;
|
||||
right: -0.5em;
|
||||
}
|
||||
|
||||
|
||||
99
src/misc/Player/video-js-skin-internal.min.css
vendored
@ -1 +1,98 @@
|
||||
.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%}
|
||||
.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%;
|
||||
}
|
||||
|
||||
@ -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,.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, 0.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,.85), transparent)
|
||||
height: 70px;
|
||||
padding-top: 20px;
|
||||
background: none;
|
||||
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-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,.25);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-load-progress {
|
||||
background: rgba(255,255,255,.25);
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-load-progress div {
|
||||
background: rgba(255,255,255,.25);
|
||||
background: rgba(255, 255, 255, 0.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: .95em;
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.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-public .vjs-subs-caps-button {
|
||||
display: none!important;
|
||||
.vjs-internal .vjs-subs-caps-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* spacer */
|
||||
@ -123,52 +123,47 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.vjs-public .vjs-overlay > a > img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 25%!important;
|
||||
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: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: right;
|
||||
top: 20px !important;
|
||||
left: 30px !important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-right {
|
||||
top: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: right;
|
||||
top: 20px !important;
|
||||
right: 30px !important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-left {
|
||||
bottom: 15px!important;
|
||||
left: 20px!important;
|
||||
text-align: left;
|
||||
bottom: 20px !important;
|
||||
left: 30px !important;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-bottom-right {
|
||||
bottom: 15px!important;
|
||||
right: 20px!important;
|
||||
text-align: left;
|
||||
bottom: 20px !important;
|
||||
right: 30px !important;
|
||||
}
|
||||
|
||||
/* context menu */
|
||||
|
||||
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
||||
background: rgba(0,0,0,.8);
|
||||
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,.25);
|
||||
background: unset !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.vjs-public .vjs-lock-open {
|
||||
z-index: 1000;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
124
src/misc/Player/video-js-skin-public.min.css
vendored
@ -1 +1,123 @@
|
||||
.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}
|
||||
.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;
|
||||
}
|
||||
|
||||