Compare commits

...

319 Commits

Author SHA1 Message Date
Ingo Oppermann
08b1dd0ba0
Fix chromecast support 2024-11-01 11:23:06 +01:00
Ingo Oppermann
5c2a3a1fa5
Merge pull request #73 from badincite/main
Fix chromecast
2024-11-01 11:09:13 +01:00
badincite
67cc21b1fa Fix chromecast
Chromecast needs to append the url origin for the source to properly pass to the cast device. And it appears to need the default receiver application ID.
2024-10-29 09:45:05 -04:00
Ingo Oppermann
c1a9c715a4
Update language files 2024-09-13 15:42:56 +02:00
Ingo Oppermann
ce2e4eb836
Bump version to 1.14.0, bundle version to 2.12.0 2024-09-13 15:39:28 +02:00
Ingo Oppermann
9e81f203cb
Add option to select which channels will be displayed on the playersite (#392, #800) 2024-09-13 15:33:47 +02:00
Ingo Oppermann
4a88f47af5
Fix docker build (#64) 2024-09-13 12:22:07 +02:00
Jan Stabenow
3f44579508 Mod updates changelog 2024-09-06 12:30:52 +02:00
Jan Stabenow
18490b0496 Add wettercom service 2024-09-06 12:28:06 +02:00
Ingo Oppermann
f0d1db9044
Merge branch 'dev' of github.com:datarhei/restreamer-ui into dev 2024-09-06 11:25:46 +02:00
Ingo Oppermann
eaacb94c54
Remove erroneous filter setting 2024-09-06 11:25:19 +02:00
Jan Stabenow
352138dfef Fix encoded address 2024-09-06 10:28:10 +02:00
Jan Stabenow
3f90be8598 Mod updates public videojs >v8 2024-09-06 09:51:00 +02:00
Ingo Oppermann
05649aa2fd
Fix double -filter parameter when encoder sets filter 2024-09-05 14:27:55 +02:00
Ingo Oppermann
f54adc6b94
Bump version to 1.13 2024-06-07 12:15:40 +02:00
Ingo Oppermann
625b080752
Add opt-in for extended list of channel layouts 2024-06-04 17:13:03 +02:00
Ingo Oppermann
1920eb583c
Add more audo channel layouts 2024-06-04 15:08:52 +02:00
Ingo Oppermann
8be8128d6e
Fix returning correct custom value in SelectCustom component 2024-06-04 14:55:46 +02:00
Jan Stabenow
88826db4fd Mod updates changelog 2024-06-02 10:01:15 +02:00
Jan Stabenow
6154b9b734 Mod enables ff-loglevel and prepares the logging component 2024-06-02 09:57:01 +02:00
Jan Stabenow
3d02d3a79b Mod updates dep. 2024-06-02 09:55:10 +02:00
Jan Stabenow
58d0292ef9 Mod reduces complexity 2024-05-24 17:10:19 +02:00
Jan Stabenow
1f04169aa5 Fix player position 2024-05-24 11:47:06 +02:00
Jan Stabenow
c128f1d3f2 Mod uses official RTMP target 2024-05-24 11:23:49 +02:00
Ingo Oppermann
fdfa0d8f6f
Update translations 2024-04-29 15:55:13 +02:00
Ingo Oppermann
9d666e0879
Fix missing stream URL, summarize streams in probe log, don't lock type for first stream 2024-04-29 15:54:17 +02:00
Ingo Oppermann
6f5ecf878c
Remove unused imports 2024-04-29 12:12:02 +02:00
Ingo Oppermann
486d64ff19
Add to allow stream hints in case probing fails 2024-04-26 21:41:09 +02:00
Ingo Oppermann
9277f04b4b
Bump version to 1.12.0 2024-04-23 14:26:13 +02:00
Ingo Oppermann
f4708c23f0
Fix comment 2024-04-23 11:38:25 +02:00
Ingo Oppermann
a87ad7d614
Fix always add probesize and analyzeduration options 2024-04-19 16:40:09 +02:00
Ingo Oppermann
8709d37738
Add option to select different SRT stream in wizard 2024-04-19 15:45:54 +02:00
Ingo Oppermann
9fad572ec0
Add option to select different RTMP stream in wizard 2024-04-19 15:45:24 +02:00
Ingo Oppermann
1ab3e39ba0
Fix datarhei/restreamer#710 2024-04-15 12:58:04 +02:00
Ingo Oppermann
ec7cc4bc47
Remove debug output 2024-04-15 12:38:38 +02:00
Ingo Oppermann
f53be95e70
Fix reset of previous audio settings when editing profile (datarhei/restreamer#730) 2024-04-15 12:12:08 +02:00
Ingo Oppermann
1e86878d75
Fix RTMP URL for receive mode 2024-04-12 15:21:59 +02:00
Ingo Oppermann
e7ace32c3c
Update languages 2024-04-04 10:06:25 +02:00
Ingo Oppermann
60d3e8f617
Rename buttons 2024-04-04 09:43:23 +02:00
Ingo Oppermann
ff2130138a
Rename decoder files 2024-04-03 21:17:54 +02:00
Ingo Oppermann
8e208789f0
Rename encoder files 2024-04-03 17:18:52 +02:00
Ingo Oppermann
7007ce71ed
Reorder encoders 2024-04-03 16:35:25 +02:00
Ingo Oppermann
06933e47d8
Enable other codecs in publication services 2024-04-03 16:34:50 +02:00
Ingo Oppermann
85a89b9b3a
Bump version to 1.11.0, update changelog 2024-04-03 14:29:42 +02:00
Ingo Oppermann
8ea3b71844
Merge branch 'main' into dev 2024-04-02 17:04:47 +02:00
Ingo Oppermann
6292e62858
Allow to stream HEVC and AV1 to Youtube via RTMP 2024-03-27 21:01:53 +01:00
Ingo Oppermann
82bd4f2d76
Add librav1e AV1 encoder 2024-03-27 21:01:20 +01:00
Ingo Oppermann
44657181a0
Rename AV1NVDEC to AV1CUVID 2024-03-27 21:00:52 +01:00
Ingo Oppermann
8f3c60a1a7
Remove console log 2024-03-27 20:58:11 +01:00
Ingo Oppermann
dc384ed554
Remove comments 2024-03-27 11:55:45 +01:00
Ingo Oppermann
12344c958f
Fix codec name, change to hevc 2024-03-27 10:46:51 +01:00
Ingo Oppermann
c73cc357b9
Add HEVC VideoToolbox encoder 2024-03-27 10:45:35 +01:00
Ingo Oppermann
a68a43ef48
Cleanup language files 2024-03-27 10:44:40 +01:00
Ingo Oppermann
b8c453bf48
Merge pull request #46 from patcarter883/dev
Update UI to support AV1 CUDA decoding.
2024-03-27 10:40:53 +01:00
patcarter883
3e4662b337 Revert unrequired change to NVDEC decoder. 2024-03-24 13:22:31 +00:00
patcarter883
a0c41bba87 AV1 decode 2024-03-24 13:18:14 +00:00
patcarter883
2226ce41c2 Merge branch 'dev' of https://github.com/patcarter883/restreamer-ui into dev 2024-03-24 10:26:08 +00:00
patcarter883
355f6a7967 Add AV1 to decoders 2024-03-24 10:25:14 +00:00
patcarter883
fa094782c7 Add AV1 to decoders 2024-03-22 12:35:45 +00:00
Ingo Oppermann
487899f934
Fix checking out correct branch 2024-02-27 11:24:00 +01:00
Ingo Oppermann
318a1e32f4
Add schedule for dev build 2024-02-26 10:14:52 +01:00
Ingo Oppermann
82796c3801
Anonymize error message (datarhei/restreamer#688) 2024-02-23 17:44:10 +01:00
Ingo Oppermann
cfb70e5325
Merge branch 'main' into dev 2024-02-23 13:07:05 +01:00
Ingo Oppermann
de6fa8d64f
Fix chromecast config (#37, datarhei/restreamer#689) 2024-02-23 13:03:21 +01:00
Ingo Oppermann
4de6e111cc
Fix version extraction 2024-02-22 21:32:31 +01:00
Ingo Oppermann
fa462d696d
Rename artifact 2024-02-22 21:09:17 +01:00
Ingo Oppermann
c2e95265ac
Remove layer cache 2024-02-22 21:07:09 +01:00
Ingo Oppermann
906adcd5c5
Update actions 2024-02-22 20:58:28 +01:00
Ingo Oppermann
9dfdc87983
Fix login action version 2024-02-22 20:50:03 +01:00
Ingo Oppermann
939635ed94
Make dev workflow manually triggered 2024-02-22 20:42:17 +01:00
Ingo Oppermann
3ef10bf093
Update workflows 2024-02-22 20:40:44 +01:00
Ingo Oppermann
78a6e68421
Add test workflow 2024-02-22 12:08:06 +01:00
Ingo Oppermann
2c88c4dbde
Add ffmpeg6 support 2024-02-05 11:08:06 +01:00
Jan Stabenow
d931bc6e05 Fix public url 2024-02-02 19:52:40 +01:00
Jan Stabenow
4e6725ae94 Fix typo 2024-02-02 18:03:26 +01:00
Jan Stabenow
92ce3b5ba7 Add PUBLIC_URL 2024-02-02 17:46:01 +01:00
Ingo Oppermann
af690c689c
Bump bundle to 2.8.0 2024-02-02 14:01:05 +01:00
Jan Stabenow
4ac941b128 Fix theme color 2024-01-26 18:03:16 +01:00
Jan Stabenow
50c325fcce Fix help 2024-01-26 17:29:04 +01:00
Jan Stabenow
adf1b0b3b9 Fix build env 2024-01-26 17:27:11 +01:00
Jan Stabenow
a1a79defd1 Mod removes comment (workflow failed) 2024-01-26 17:19:31 +01:00
Jan Stabenow
6b6efffe9f Mod parse ENV to GITHUB_ENV 2024-01-26 17:14:43 +01:00
Jan Stabenow
7bc922f1d5 Mod replaces env injection 2024-01-26 15:35:26 +01:00
Jan Stabenow
77dfcbe749 Merge branch 'dev' of https://github.com/datarhei/restreamer-ui into dev 2024-01-26 15:19:39 +01:00
Jan Stabenow
813252d0c0 Mod removes matrix build on workflow_dispatch 2024-01-26 15:19:33 +01:00
Jan Stabenow
7d7c09c870 Mod adds env 2024-01-26 15:19:00 +01:00
Ingo Oppermann
3270140555
Fix usage of initSettings in Network source 2024-01-26 15:12:12 +01:00
Jan Stabenow
febf34e8e4 Mod updates env 2024-01-26 15:00:58 +01:00
Jan Stabenow
ee2cc8eca7 Add CADDY_IMAGE env 2024-01-26 15:00:40 +01:00
Jan Stabenow
88e16f187d Mod updates npm 2024-01-26 14:10:35 +01:00
Jan Stabenow
ce1589ecde Mod uses :dev tag 2024-01-26 13:39:36 +01:00
Jan Stabenow
6d6028125c Merge branch 'dev' of https://github.com/datarhei/restreamer-ui into dev 2024-01-26 13:13:27 +01:00
Jan Stabenow
d98ff905b0 Mod removes matrix build 2024-01-26 13:13:21 +01:00
Ingo Oppermann
fda9f2adda
Bump to version v1.10.0 2024-01-26 13:04:36 +01:00
Jan Stabenow
80c01f93ad Merge branch 'dev' of https://github.com/datarhei/restreamer-ui into dev 2024-01-26 13:02:34 +01:00
Jan Stabenow
e92f87544e Fix docker tag 2024-01-26 13:02:22 +01:00
Ingo Oppermann
53e809685c
Merge branch 'dev' of github.com:datarhei/restreamer-ui into dev 2024-01-26 12:54:02 +01:00
Ingo Oppermann
ff79efca02
Set channelid as default push stream name 2024-01-26 12:53:32 +01:00
Jan Stabenow
12ab148f47 Fix multiarch build 2024-01-26 12:51:12 +01:00
Ingo Oppermann
d74438e300
Allow to select from publishing RTMP and SRT streams 2024-01-19 20:33:48 +01:00
Ingo Oppermann
42013ec2c1
Allow RTSPS protocol (datarhei/restreamer#677) 2024-01-19 12:41:02 +01:00
Ingo Oppermann
4854c63fb1
Add audio loop source 2023-12-15 14:41:37 +01:00
Ingo Oppermann
c1f9b95a08
Fix RTMPS address with custom ports (datarhei/restreamer#658) 2023-12-15 14:01:40 +01:00
Ingo Oppermann
bcd3b7ba52
Add resource usage and command to process details 2023-12-15 12:57:14 +01:00
Ingo Oppermann
f4c9fbe61a
Fix wrongly displayed SRT URL (datahrei/restreamer#635) 2023-12-04 12:30:52 +01:00
Jan Stabenow
dae04e7882 Mod bump videojs + adds poster-link 2023-12-01 18:20:41 +01:00
Ingo Oppermann
9752376b4a
Update changelog 2023-12-01 14:10:08 +01:00
Jan Stabenow
f57e1f6365 Mod updates locals 2023-11-30 22:59:15 +01:00
Jan Stabenow
6f34336c32 Fix login error + add dailymotion service 2023-11-28 19:54:09 +01:00
Jan Stabenow
729ad48cc4 Fix build + bump version 2023-11-28 14:31:06 +01:00
Jan Stabenow
5e2dff2a7e Mod for release preparation 2023-11-28 00:05:43 +01:00
Jan Stabenow
3c468887f7 Add kick.com + mod. twitch server list 2023-11-27 23:55:27 +01:00
Jan Stabenow
6a24e68055 Add enlarged channel overview 2023-11-27 17:02:39 +01:00
Jan Stabenow
f809eacabf Fix Owncast typo 2023-11-20 22:56:40 +01:00
Jan Stabenow
72312312ca Fix Restream grid 2023-11-20 22:56:19 +01:00
Jan Stabenow
62bc085ee4 Add new publication services: Rumble, PicartoTV, NimoTV, Livepush 2023-11-20 22:54:32 +01:00
Ingo Oppermann
287b50dab2
Add to allow to loop video files 2023-11-17 14:52:04 +01:00
Ingo Oppermann
db1b5227fb
Allow to set limits for ingest and egress processes 2023-11-10 15:38:47 +01:00
Ingo Oppermann
c263f1f0d4
Use UploadButton component for playersite 2023-11-06 17:03:43 +01:00
Ingo Oppermann
a85c5241f9
Add refresh button for raspicam input devices 2023-11-06 16:38:15 +01:00
Ingo Oppermann
c316e36c1d
Add basic image loop input source 2023-11-06 16:34:47 +01:00
Ingo Oppermann
245f69cdcb
Add option for custom poster image in player 2023-11-03 16:05:56 +01:00
Jan Stabenow
a68a39b9f9 Add a/v filter to the publication components - #593 2023-10-23 13:34:19 +02:00
Jan Stabenow
e579b842f3 Mod updates changelog 2023-10-23 13:15:07 +02:00
Jan Stabenow
8565350b66 Fix locales 2023-10-16 17:12:50 +02:00
Jan Stabenow
377acf12ff Fix videojs skin 2023-10-16 16:06:48 +02:00
Jan Stabenow
a7d2ac4ec7 Merge branch 'dev' of https://github.com/datarhei/restreamer-ui into dev 2023-10-16 13:45:25 +02:00
Jan Stabenow
964a7cd745 Add ignores eslint-hint on lingui exports 2023-10-16 13:45:18 +02:00
Ingo Oppermann
5dd710fc47
Fix detecting Raspberyy Pi camera v4l2 device, changing default format to yuv420p 2023-10-16 13:39:39 +02:00
Jan Stabenow
46c3ac5139 Mod updates packages + fix tests + updates lingui-files 2023-10-16 10:53:18 +02:00
Ingo Oppermann
042029cb2e
Update CHANGELOG 2023-09-29 14:05:04 +02:00
Ingo Oppermann
546024fc90
Fix ALSA demuxer option names 2023-09-29 14:04:12 +02:00
Ingo Oppermann
bffb228b1d
Fix index out-of-range warning, list ALSA devices for Raspicam video source 2023-09-29 14:03:27 +02:00
Ingo Oppermann
5223e27079
Fix MUI warning 2023-09-29 14:01:11 +02:00
Ingo Oppermann
92059f7579
Update CHANGELOG 2023-09-22 15:20:55 +02:00
Ingo Oppermann
b089edc02a
Merge branch 'mdastgheib-feature/referer/header/field' into dev 2023-09-22 15:11:48 +02:00
Ingo Oppermann
74c71445b4
Fix tests 2023-09-22 15:11:10 +02:00
Ingo Oppermann
7888247f23
Rename Referer to Referrer in the label 2023-09-22 15:10:08 +02:00
Ingo Oppermann
39dfd348db
Merge branch 'feature/referer/header/field' of https://github.com/mdastgheib/restreamer-ui into mdastgheib-feature/referer/header/field 2023-09-22 14:43:38 +02:00
Ingo Oppermann
79610e2e4e
Fix wrong ENV label in settings 2023-09-22 14:40:11 +02:00
Ingo Oppermann
0535ad8f51
Update video.js, avoid error if overlay is not defined 2023-09-22 14:38:04 +02:00
mdastgheib
763618cc4f feat: Added UI advanced options '-referer' header functionality 2023-09-20 21:35:03 -05:00
Ingo Oppermann
bda1e18619
Update changelog 2023-09-06 15:40:13 +02:00
Ingo Oppermann
1a44e78eb7
Fix import path 2023-09-06 15:37:06 +02:00
Ingo Oppermann
d4edd0e399
Merge pull request #38 from orryverducci/frame-interpolation
Add frame interpolation (framerate) filter
2023-09-06 15:16:51 +02:00
Ingo Oppermann
94eb24c952
Merge pull request #39 from orryverducci/mpegts-fixes
MPEG-TS fixes
2023-09-06 15:13:30 +02:00
Orry Verducci
8de6830f3f Correct changelog order 2023-08-03 14:10:06 +01:00
Orry Verducci
28521b7a52 Add MPEG-TS fix to changelog 2023-08-03 12:42:17 +01:00
Orry Verducci
fec59ef28b MPEG-TS syntax fixes 2023-08-03 12:04:38 +01:00
Orry Verducci
9cc06d6ce6 Add framerate filter 2023-07-31 13:47:30 +01:00
Jan Stabenow
93316b938b
Mod uses placeholders for ingress setups #560 2023-05-10 18:33:15 +02:00
Ingo Oppermann
3d9d663e35
Merge branch 'main' into dev 2023-05-08 13:06:55 +02:00
Ingo Oppermann
a87a02aaff
Add Ukrainian translation 2023-05-08 11:35:48 +02:00
Jan Stabenow
4ff4c47ae2
Mod bumps to v1.8.0 2023-05-08 11:24:40 +02:00
Jan Stabenow
5512154a22
Mod updates dep. 2023-04-18 09:54:19 +02:00
Jan Stabenow
e4676fded7
Fix empty force_key_frames value 2023-04-18 08:59:12 +02:00
Ingo Oppermann
f333d7fe95
Fix Icecast publication service
With Icecast it is possible to output only an audio track. However,
the tooling required always a video track and was throwing an error
that was not properly handled. Now audio-only streams are allowed
for publication services.

datarhei/restreamer#429
datarhei/restreamer#483
datarhei/restreamer#542
2023-04-13 12:10:20 +02:00
Ingo Oppermann
0da1d6ba49
Add Chinese (simplified) translation 2023-03-24 11:52:00 +01:00
Jan Stabenow
30751629fd
Fix imprint, terms and credit without share (datarhei/restreamer#529) 2023-03-21 14:15:39 +01:00
Jan Stabenow
7afd632544
Fix security hints (npm deps) 2023-03-21 14:12:57 +01:00
Jan Stabenow
09b3630298
Merge pull request #26 from datarhei/snyk-upgrade-d3c0541c7c428215921ad5988402bbf8
[Snyk] Upgrade @lingui/macro from 3.17.1 to 3.17.2
2023-03-20 14:06:23 +01:00
Jan Stabenow
abae3696c9
Merge branch 'main' into snyk-upgrade-d3c0541c7c428215921ad5988402bbf8 2023-03-20 14:06:09 +01:00
Jan Stabenow
f42e50dd95
Merge pull request #25 from datarhei/snyk-upgrade-8ab9ef4c95b5869fc706e61a055eefa4
[Snyk] Upgrade video.js from 8.0.4 to 8.1.0
2023-03-20 13:57:42 +01:00
Jan Stabenow
41d166a67f
Merge pull request #27 from datarhei/snyk-upgrade-9290ad440656b2d252dbde8006e7e26c
[Snyk] Upgrade @lingui/react from 3.17.1 to 3.17.2
2023-03-20 13:56:59 +01:00
snyk-bot
87f4e443b5
fix: upgrade @lingui/react from 3.17.1 to 3.17.2
Snyk has created this PR to upgrade @lingui/react from 3.17.1 to 3.17.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/j.stabenow/project/204cdfb1-d11d-4cbe-87df-f862f7880c39?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-03-18 01:59:38 +00:00
snyk-bot
99c90b4cab
fix: upgrade @lingui/macro from 3.17.1 to 3.17.2
Snyk has created this PR to upgrade @lingui/macro from 3.17.1 to 3.17.2.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/j.stabenow/project/204cdfb1-d11d-4cbe-87df-f862f7880c39?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-03-18 01:59:35 +00:00
Jan Stabenow
42f918baf2
Fix proxy error on playersite (datarhei/restreamer#525) 2023-03-17 18:07:24 +01:00
snyk-bot
fcd4231a3c
fix: upgrade video.js from 8.0.4 to 8.1.0
Snyk has created this PR to upgrade video.js from 8.0.4 to 8.1.0.

See this package in npm:


See this project in Snyk:
https://app.snyk.io/org/j.stabenow/project/204cdfb1-d11d-4cbe-87df-f862f7880c39?utm_source=github&utm_medium=referral&page=upgrade-pr
2023-03-16 20:42:24 +00:00
Ingo Oppermann
cef6efbac0
Add stream key field and protocol detection to RTMP publication service 2023-03-14 12:52:10 +01:00
Ingo Oppermann
718b00fe97
Fix saving RTMP advanced options (datarhei/restreamer#518) 2023-03-08 09:34:39 +01:00
Ingo Oppermann
113fe20fc2
Fix help buttons for other languages than English and German (#24) 2023-02-24 09:57:59 +01:00
Jan Stabenow
3d337541ce
Fix internal player skin (volume bar) 2023-02-23 16:19:53 +01:00
Ingo Oppermann
836f06b746
Merge branch 'dev' 2023-02-23 11:48:57 +01:00
Jan Stabenow
7a56fdeeaf
Mod bumps versions and updates npm 2023-02-23 10:03:44 +01:00
Ingo Oppermann
3735ac2242
Update translations 2023-02-23 09:30:12 +01:00
Jan Stabenow
168b1fbaa3
Add analyzeduration, probesize and max_probe_packets input options 2023-02-23 08:59:35 +01:00
Ingo Oppermann
ca966fbcba
Fix translations 2023-02-22 15:22:24 +01:00
Ingo Oppermann
7538e083e3
Update dependencies 2023-02-22 15:21:48 +01:00
Jan Stabenow
2f78a621fe
Fix tests 2023-02-22 13:03:39 +01:00
Jan Stabenow
3961343446
Fix Creative Commons icons 2023-02-22 12:24:50 +01:00
Jan Stabenow
3da606c8dd
Add avoid_negative_ts and http_proxy input options,updates npm dependencies 2023-02-22 12:08:27 +01:00
Jan Stabenow
b39e04ec68
Add copyts, start_at_zero and use_wallclock_as_timestamps option 2023-02-14 16:53:05 +01:00
Ingo Oppermann
8e07fcef80
Fix wrong link to service maintainer (#23) 2023-01-16 09:40:56 +01:00
Ingo Oppermann
43e691b9ce
Update changelog 2023-01-12 12:18:06 +01:00
Ingo Oppermann
e299dfd3f6
Add Greek translation 2023-01-12 12:15:18 +01:00
Ingo Oppermann
cfd71d7b9b
Add Slovenian translation (thanks to Grega) 2023-01-12 12:09:43 +01:00
Ingo Oppermann
085f6fe4d1
Add Danish translation (Thanks to Filip Stadler and Info) 2023-01-12 12:03:06 +01:00
Ingo Oppermann
94f7715b2b
Add Turkish translation (thanks to Ramazan Sancar, #22) 2023-01-12 11:54:02 +01:00
Ingo Oppermann
b34906ce91
Update translations 2023-01-12 11:39:44 +01:00
Ingo Oppermann
d7f64ccfef
Add heuristic to find core address if UI is proxied 2022-11-29 12:28:11 +01:00
Jan Stabenow
5dc24dca88
Mod updates changelog 2022-11-25 15:32:04 +01:00
Jan Stabenow
29ee83a4ce
Fix positioning of the deinterlaces filter (datarhei/restreamer#465) 2022-11-25 15:29:21 +01:00
Ingo Oppermann
8b402acbbe
Merge branch 'dev' 2022-11-24 18:25:38 +01:00
Jan Stabenow
ab0ba51802
Mod bump version to 1.6.0 2022-11-24 17:10:16 +01:00
Jan Stabenow
3b9aabd2be
Mod increases test timeout 2022-11-24 17:01:39 +01:00
Ingo Oppermann
eb89e6b942
Update changelog 2022-11-24 15:34:27 +01:00
Ingo Oppermann
f2b8071c70
Update German translations 2022-11-24 15:33:55 +01:00
Jan Stabenow
39d9002076
Mod removes duplicate labels 2022-11-24 15:02:34 +01:00
Ingo Oppermann
a557e3afdb
Update changelog 2022-11-24 12:57:01 +01:00
Jan Stabenow
64d908ceec
Mod updates dep. 2022-11-24 12:50:08 +01:00
Ingo Oppermann
54fc55fc18
Update changelog, bump version to 1.6.0 2022-11-24 12:46:49 +01:00
Ingo Oppermann
d18f99acdd
Add coder tests 2022-11-24 12:41:40 +01:00
Ingo Oppermann
638642d3de
Fix missing parameters for coder defaults 2022-11-24 10:26:56 +01:00
Ingo Oppermann
ec4edf2f47
Fix wrong call to encoder defaults (datarhei/restreamer#467) 2022-11-22 16:49:21 +01:00
Jan Stabenow
d6b19e0e0f
Mod cleanup bwdiff 2022-11-21 10:55:09 +01:00
Jan Stabenow
686e386f41
Add Bob Weaver Deinterlacing Filter (datarhei/restreamer#465) 2022-11-21 10:52:04 +01:00
Ingo Oppermann
410a91d3c7
Add Korean translation (thanks to Jihaeng) 2022-11-18 10:38:04 +01:00
Ingo Oppermann
98b827b9f3
Refine network source test 2022-11-18 10:35:06 +01:00
Ingo Oppermann
a844bf362d
Add some testing hints 2022-11-16 16:45:02 +01:00
Ingo Oppermann
491124df1f
Add more tests for the network source 2022-11-16 14:31:45 +01:00
Ingo Oppermann
14e217172b
Fix listing of push options if none of the protocols are available 2022-11-16 14:30:59 +01:00
Ingo Oppermann
233393400d
Add test for network source 2022-11-15 21:49:26 +01:00
Ingo Oppermann
6638b42e72
Merge branch 'dev' 2022-11-14 21:49:56 +01:00
Jan Stabenow
7fcc5bb65d
Del test build 2022-11-14 21:34:16 +01:00
Jan Stabenow
1b9cfa279f
Fix requires Core >= v16.11.0 and FFmpeg >= 5.1.0 2022-11-14 21:21:37 +01:00
Ingo Oppermann
1d25b84ac5
Update changelog and bump version to 1.5.1 2022-11-14 21:03:36 +01:00
Ingo Oppermann
e0982a1941
Fix FFmpeg version check for RTSP sources (datarhei/restreamer#455) 2022-11-13 21:14:37 +01:00
Ingo Oppermann
8a45e4c41c
Merge branch 'dev' 2022-11-11 15:18:46 +01:00
Ingo Oppermann
eac85115c2
Update German translation 2022-11-11 12:36:54 +01:00
Jan Stabenow
268ca46669
Fix typo 2022-11-11 11:00:14 +01:00
Ingo Oppermann
d2b39753d2
Update German translation 2022-11-11 10:27:33 +01:00
Ingo Oppermann
c91fc9d2fe
Fix typos 2022-11-11 10:17:20 +01:00
Jan Stabenow
e45f9f0f98
Mod OMX hint 2022-11-11 10:13:00 +01:00
Jan Stabenow
3920d37c7a
Fix TypeError 2022-11-11 09:26:27 +01:00
Jan Stabenow
7456ed515a
Mod update 2022-11-11 00:29:25 +01:00
Jan Stabenow
4099f463fa
Mod expands V4L2_M2M options (an unstable RPI 64bit encoder) 2022-11-11 00:28:18 +01:00
Jan Stabenow
8e7ec934d3
Mod indicates a faulty cache configuration 2022-11-11 00:27:58 +01:00
Ingo Oppermann
731d4a93d8
Fix warnings navigating away during component mount 2022-11-10 16:43:57 +01:00
Ingo Oppermann
4ec02d1b2d
Fix ChannelSelect routing 2022-11-10 13:45:02 +01:00
Ingo Oppermann
3a5a91c2c9
Fix using channel ID instead of process ID 2022-11-09 21:20:06 +01:00
Jan Stabenow
3610a2af5a
Mod bump to v1.5.0 2022-11-09 15:07:17 +01:00
Ingo Oppermann
40594b2e51
Fix checking for fresh install 2022-11-09 14:23:54 +01:00
Ingo Oppermann
500faeb2a7
Load changelog only if it will be displayed 2022-11-09 11:20:36 +01:00
Ingo Oppermann
4f01a364d3
Fix display of changelog 2022-11-09 11:14:47 +01:00
Jan Stabenow
7ea182ce05
Mod changelog 2022-11-09 11:05:58 +01:00
Jan Stabenow
e238ae6119
Mod style 2022-11-09 09:52:07 +01:00
Jan Stabenow
cda68a89fb
Mod format 2022-11-08 21:25:24 +01:00
Ingo Oppermann
b969b1661d
Update German translation 2022-11-08 20:46:31 +01:00
Jan Stabenow
f9ae5d6527
Fix formatting 2022-11-08 16:41:15 +01:00
Jan Stabenow
71bfb9d1f1
Mod removes armv6 from :dev 2022-11-08 16:33:58 +01:00
Jan Stabenow
f6e9328ff5
Mod switches to yarn berry 2022-11-08 15:30:38 +01:00
Ingo Oppermann
e795c2b33e
Move component to common collection to avoid duplication 2022-11-08 11:31:52 +01:00
Jan Stabenow
f89aad775f
Mod updates npm dep. + bump bundle version 2022-11-08 11:19:38 +01:00
Jan Stabenow
7f18e29754
Fix yarn ESOCKETTIMEDOUT 2022-11-08 10:39:46 +01:00
Jan Stabenow
6552303505
Mod removes maintained node versions (build failer) 2022-11-08 10:19:38 +01:00
Ingo Oppermann
38036dc0eb
Remove changelog from public folder 2022-11-08 09:36:14 +01:00
Jan Stabenow
5e09f41707
Mod switches to the improved SRT syntax 2022-11-08 01:18:54 +01:00
Jan Stabenow
8ed6605fc3
Mod changelog style 2022-11-08 00:17:35 +01:00
Jan Stabenow
36c72ab6cc
Fix default fps_mode 2022-11-07 23:15:07 +01:00
Jan Stabenow
b4b8929a0d
Mod expands scaling options 2022-11-07 22:38:51 +01:00
Ingo Oppermann
fb650243da
Add skills props to encoder and decoder components 2022-11-07 22:15:38 +01:00
Jan Stabenow
0edf69ed7c
Mod improves display of progress data 2022-11-07 17:29:34 +01:00
Jan Stabenow
44f7377669
Add fps_mode (ff5) 2022-11-07 17:23:11 +01:00
Ingo Oppermann
1924499cdb
Remove support for clappr player 2022-11-07 10:45:55 +01:00
Ingo Oppermann
167efb399e
Upgrade react-scripts 2022-11-07 10:42:59 +01:00
Jan Stabenow
1ce00926d0
Mod deprecated param ocl - now ochl (ff5) 2022-11-07 10:31:43 +01:00
Jan Stabenow
3e0d0739e5
Mod cleanup 2022-11-07 10:16:37 +01:00
Ingo Oppermann
cb9b59a029
Add changelog notification dialog
Expecting the CHANGELOG.md in the UI webroot. The markdown is
parsed in order to find the sections with the versions. A section
for a version has to start with "### " followed by the version
(either with the v prefix or not). Only the sections for the
relevant versions from the previous to the current will be displayed.
2022-11-04 11:50:37 +01:00
Ingo Oppermann
1fffde2e41
Upgrade to React 18 2022-11-04 11:44:07 +01:00
Ingo Oppermann
157d06c8b9
Update language files 2022-11-03 15:41:04 +01:00
Jan Stabenow
7e331d5f82
Mod simplifies the setup of Restreamer-to-Restreamer connections 2022-10-21 22:15:36 +02:00
Jan Stabenow
c008fb1eef
Add scale filter to non-hwaccel encoders 2022-10-21 00:31:41 +02:00
Jan Stabenow
c367261053
Add PeerTube and Media Network to publication services (plattforms, software) 2022-10-19 20:56:58 +02:00
Jan Stabenow
f836a244bf
Mod adds Istafeed.me as StreamKey service to Instagram's publishing service 2022-10-19 20:56:07 +02:00
Jan Stabenow
af9603575a
Mod renames "Low delay" to "Low latency (buffer)" and set false as default 2022-10-19 20:52:38 +02:00
Jan Stabenow
24b14a2d75
Add reset button to hide a player logo (datarhei/restreamer#431) 2022-10-19 20:39:33 +02:00
Jan Stabenow
b5967f24e6
Fix videojs-overlay logo size (datarhei/restreamer#431) 2022-10-19 18:37:07 +02:00
Jan Stabenow
9d3fdb5c41
Fix Icecast publication service settings (datarhei/restreamer#429) 2022-10-17 22:28:15 +02:00
Ingo Oppermann
76ac60cc5b
Provide required config version 2022-10-11 18:02:18 +02:00
Ingo Oppermann
c5a91815fa
Fix use of TLS for input from local RTMP server 2022-10-11 15:42:18 +02:00
Ingo Oppermann
df9ae03900
Fix use of TLS for input from local RTMP server 2022-10-11 15:39:52 +02:00
Jan Stabenow
e587f2076b
Fix removes SRT bitstream on tee (OBS > RTMP > SRT is faulty) 2022-10-02 15:30:29 +02:00
Ingo Oppermann
8a0521846b
Merge branch 'dev' 2022-09-30 15:08:27 +02:00
Jan Stabenow
7a8a04c442
Mod updates build env. 2022-09-30 15:05:18 +02:00
Ingo Oppermann
afe79a21a6
Update required Core version 2022-09-30 14:01:14 +02:00
Ingo Oppermann
a1bfd104ae
Bump version to 1.4.0 2022-09-30 12:40:00 +02:00
Ingo Oppermann
f17112a97f
Add email field for Let's Encrypt certification 2022-09-30 12:38:11 +02:00
Ingo Oppermann
dc84aae8ce
Require core version 16.11.0 2022-09-30 12:36:10 +02:00
Jan Stabenow
7f1d2c7df1
Mod uses node:18.9.0-alpine3.15 (no armv7 support) 2022-09-30 10:20:07 +02:00
Jan Stabenow
d348e702a2
Update Build.restreamer-ui.env 2022-09-30 10:09:26 +02:00
Ingo Oppermann
0795d5fba8
Merge branch 'dev' 2022-09-30 10:01:26 +02:00
Jan Stabenow
4017c1d2a3
Fix SRT bitstream on tee 2022-09-30 09:52:51 +02:00
Jan Stabenow
a59c766c4d
Add low_delay option + uses the ingest stream for publication 2022-09-29 18:26:06 +02:00
Jan Stabenow
2cb2881e3c
Mod updates changelog 2022-09-29 10:12:50 +02:00
Jan Stabenow
3cfec164bd
Add dlive + trovo publication services 2022-09-28 11:50:39 +02:00
Jan Stabenow
183717e98a
Add DVR optimizations, updates packages and fix typo 2022-09-21 14:11:20 +02:00
Jan Stabenow
42ca7d2ae2
Mod removes http method from diskfs 2022-08-26 14:02:47 +02:00
Jan Stabenow
d976461ab0
Mod updates packages 2022-08-26 13:40:16 +02:00
Jan Stabenow
759886afda
Add hls strftime segments (datarhei/restreamer#381) 2022-08-25 21:37:02 +02:00
Ingo Oppermann
629d8feed5
Update changelog, bump version number 2022-08-22 11:00:40 +03:00
Ingo Oppermann
59cc4c67e8
Fix viewer count (datarhei/restreamer#394) 2022-08-19 11:23:02 +03:00
Ingo Oppermann
a91ced0e6d
Merge branch 'dev' of github.com:datarhei/restreamer-ui into dev 2022-08-18 16:10:55 +03:00
Ingo Oppermann
759de38c8c
Fix user registration if username and/or password are set via environment (#13) 2022-08-18 16:10:32 +03:00
Ingo Oppermann
c9b8b6abb2
Merge pull request #14 from datarhei/dockerfile
Reduce size, serve production build
2022-08-18 14:53:29 +03:00
Ingo Oppermann
3dae99a474
Reduce size, serve production build 2022-08-18 09:04:47 +03:00
Ingo Oppermann
7ffe697939
Implement disk cache allow and block list, require core version 16.10.0 2022-08-17 17:23:28 +03:00
Ingo Oppermann
2ac8bb5e87
Merge branch 'dev' 2022-07-21 22:18:02 +02:00
Jan Stabenow
b2a6a5d3a9
Fix public_url 2022-07-21 22:15:00 +02:00
Jan Stabenow
31cc0ec139
Mod bump version 2022-07-21 21:59:59 +02:00
Ingo Oppermann
781a7ce0ec
Fix service ID, reject invalid service IDs 2022-07-21 20:20:02 +02:00
Jan Stabenow
9b86bca643
Mod changes wording, updates locals, fixes hls-version 7 failer, adds help links & formats files 2022-07-21 18:33:41 +02:00
Jan Stabenow
2c480dc67a
Mod also adds vsync drop 2022-07-21 17:15:31 +02:00
Jan Stabenow
bdc212679a
Fix Malformed AAC bitstream detected for hls version 7 @ tee 2022-07-21 17:14:33 +02:00
Jan Stabenow
2ab458ca53
Mod should fix tee_muxer problem with v4l2_h264+audio 2022-07-21 15:57:46 +02:00
Ingo Oppermann
f4ab7f32b9
Update translations 2022-07-21 14:54:06 +02:00
Ingo Oppermann
dae3452a3f
Parametrize list of protocols 2022-07-21 13:55:08 +02:00
Ingo Oppermann
0ee4e42360
Include SRT into viewer stats 2022-07-21 13:46:13 +02:00
Ingo Oppermann
2f40d96412
Show filters only if they actually used 2022-07-21 13:45:36 +02:00
Jan Stabenow
67aad79bfe
Mod wording 2022-07-20 18:50:42 +02:00
Jan Stabenow
dec533b5b7
Fix raspicam tee_muxer failer (Non-monotonous DTS in output stream) 2022-07-20 18:49:35 +02:00
Ingo Oppermann
2bf30ca450
Fix wording 2022-07-20 16:26:08 +02:00
Ingo Oppermann
b3722ec5cb
Enforce slash at the beginning of RTMP app 2022-07-20 12:44:58 +02:00
Ingo Oppermann
d7dd3b0969
Update playersite on saving a channel (datarhei/restreamer#362)
In case the playersite is enabled and the metadata of a channel e.g. its name
changes, the playersite needs also to be updated in order to reflect this
change.

As a consequence the playersite has to be enabled explicitely otherwise it will
get enabled without the user wanting it.
2022-07-20 09:21:06 +02:00
Jan Stabenow
b4ecf7309a
Fix drops static env 2022-07-19 17:01:06 +02:00
Jan Stabenow
d19c96ac3f
Add dev build 2022-07-19 16:58:36 +02:00
Jan Stabenow
b952e4680f
Mod changes field desc. 2022-07-19 16:53:11 +02:00
Ingo Oppermann
94f5d9e1ee
Fix display of current viewers
With the HLS master manifest the effective stream name is not anymore
the channelid but the channelid with a sufix. By matching the current
sessions with the channelid this wasn't taken into account.
2022-07-19 09:57:17 +02:00
Ingo Oppermann
5eb60f2b19
Fix local SRT address for pulling 2022-07-19 09:02:26 +02:00
Jan Stabenow
4e04c1caec
Fix the master_pl in tee_muxer 2022-07-18 19:26:58 +02:00
265 changed files with 84276 additions and 47789 deletions

View File

@ -10,3 +10,4 @@ node_modules/
.github
.github_build
.build
NONPUBLIC/

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
src/locales/*/messages.js

View File

@ -1,61 +1,88 @@
name: 'Build restreamer-ui'
name: 'Build main restreamer-ui'
on:
workflow_dispatch:
push:
branches-ignore:
- '**'
workflow_dispatch:
jobs:
docker:
runs-on: [self-hosted]
steps:
- name: Checkout
uses: actions/checkout@v2
build-frontend:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.latestversion.outputs.version }}
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: cardinalby/export-env-action@v1
with:
envFile: '.github_build/Build.restreamer-ui.env'
export: 'true'
expandWithJobEnv: 'true'
expand: 'true'
- name: Get latest version from package.json
id: latestversion
run: |
echo "version=$(cat ./package.json | jq '.version' | sed 's/\"//g')" >> "$GITHUB_OUTPUT"
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '21'
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Build React App
run: |
yarn install
yarn build
env:
PUBLIC_URL: './'
- name: Cache Docker layers
uses: actions/cache@v2
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Upload React build as artifact
uses: actions/upload-artifact@v4
with:
name: restreamerui-main-build
path: build/
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
build-docker:
needs: build-frontend
runs-on: [self-hosted]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build Multi-Arch
uses: docker/build-push-action@v2
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile
build-args: |
PUBLIC_URL=/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
- 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 }}

View File

@ -0,0 +1,88 @@
name: 'Build dev restreamer-ui'
on:
workflow_dispatch:
workflow_call:
schedule:
- cron: '37 4 * * *'
push:
branches:
- dev
jobs:
build-frontend:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
ref: dev
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '21'
- name: Build React App
run: |
yarn install
yarn build
env:
PUBLIC_URL: './'
- name: Upload React build as artifact
uses: actions/upload-artifact@v4
with:
name: restreamerui-dev-build
path: build/
build-docker:
needs: build-frontend
runs-on: [self-hosted]
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Download React build artifact
uses: actions/download-artifact@v4
with:
name: restreamerui-dev-build
path: build
- name: Docker meta
id: meta
uses: docker/metadata-action@v5
with:
images: |
datarhei/restreamer-ui
tags: |
type=raw,value=dev
- name: Set up QEMU
uses: docker/setup-qemu-action@master
with:
platforms: all
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@master
- name: Login to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker Image
uses: docker/build-push-action@v5
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./Dockerfile.workflow
build-args: |
CADDY_IMAGE=caddy:2.7.6-alpine
platforms: linux/amd64,linux/arm64,linux/arm/v7
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@ -1,2 +0,0 @@
# RESTREAMER UI
RELEASE=1.1.0

2
.gitignore vendored
View File

@ -1,6 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/NONPUBLIC
/node_modules
/.pnp
.pnp.js
@ -12,6 +13,7 @@
/build
# misc
NONPUBLIC
.DS_Store
.VSCodeCounter
.env.local

View File

@ -14,12 +14,19 @@
"sourceLocale": "en",
"locales": [
"en",
"da",
"de",
"el",
"es",
"fr",
"it",
"ko",
"pl",
"pt",
"es",
"ru"
"pt-br",
"ru",
"sl",
"tr",
"uk",
"zh-hans"
]
}

View File

@ -1,6 +1,170 @@
# Restreamer-UI
#### v1.1.0 > v1.2.0
## v1.13.0 > v1.14.0
- Add wettercom service
- Add option to select which channels will be displayed on the playersite ([#392](https://github.com/datarhei/restreamer/issues/392), [#800](https://github.com/datarhei/restreamer/issues/800))
- Mod updates public videojs >v8
- Fix erroneous filter setting
- Fix encoded address
- Fix double -filter parameter when encoder sets filter
- Fix Docker build ([#64](https://github.com/datarhei/restreamer-ui/issues/64))
## v1.12.0 > v1.13.0
- Add to allow stream hints in case probing fails
- Mod enables ff-loglevel and prepares the logging component
- Mod uses official Instagram-RTMP target
- Mod Remove unused imports
- Mod Update translations
- Mod updates dep.
- Fix player position
- Fix missing stream URL, summarize streams in probe log, don't lock type for first stream
## v1.11.0 > v1.12.0
- Add option to select different SRT stream in wizard
- Add option to select different RTMP stream in wizard
- Fix selecting other than first audio stream ([#710](https://github.com/datarhei/restreamer/issues/710))
- Fix reset of previous audio settings when editing profile ([#730](https://github.com/datarhei/restreamer/issues/730))
- Fix RTMP URL for receive mode
## v1.10.0 > v1.11.0
- Add allow to stream HEVC and AV1 to Youtube via RTMP
- Add librav1e AV1 encoder
- Add support for AV1 CUDA decoding ([PR 46](https://github.com/datarhei/restreamer-ui/pull/46))
- Add FFmpeg 6 support
- Add HEVC VideoToolbox encoder
- Fix anonymize error message ([#688](https://github.com/datarhei/restreamer/issues/688))
- Fix chromecast config ([#37](https://github.com/datarhei/restreamer-ui/issues/37))
## v1.9.0 > v1.10.0
- Add resource usage and ffmpeg command to process details
- Add audio loop source
- Add to allow to select from already publishing RTMP and SRT streams
- Fix wrongly displayed SRT URL ([#635](https://github.com/datarhei/restreamer/issues/635))
- Fix RTMPS address with custom ports ([#658](https://github.com/datarhei/restreamer/issues/658))
- Fix allow RTSPS protocol ([#677](https://github.com/datarhei/restreamer/issues/677))
## v1.8.0 > v1.9.0
- Add enlarged channel overview
- Add new publication services: Dailymotion, Livepush, kick.com, NimoTV, PicartoTV, Rumble
- Add frame interpolation (framerate) filter (thanks to orryverducci)
- Add -referer option for pulling HTTP streams ([PR 40](https://github.com/datarhei/restreamer-ui/pull/40), thanks to mdastgheib)
- Add a/v filter to the publication components ([#593](https://github.com/datarhei/restreamer-ui/issues/593))
- Add video or image loop as input ([#528](https://github.com/datarhei/restreamer/discussions/528))
- Add option for custom poster image in player ([#632](https://github.com/datarhei/restreamer/issues/632))
- Add option to allow to set limits for ingest and egress processes ([#636](https://github.com/datarhei/restreamer/issues/636))
- Mod extends twitch's server list
- Mod uses placeholders for ingress setups ([#560](https://github.com/datarhei/restreamer-ui/issues/560))
- Mod updates npm
- Fix Owncast typo
- Fix Restream grid
- Fix the advanced settings in the MPEG-TS publication service ([#597](https://github.com/datarhei/restreamer/issues/597), thanks to orryverducci)
- Fix ALSA demuxer option names
- Fix index out-of-range warning, list ALSA devices for Raspicam video source
- Fix MUI warning
- Fix videojs skin
## v1.7.0 > v1.8.0
- Add stream key field and protocol detection to RTMP publication service
- Add Chinese (simplified) translation (thanks to Huyg0180110559)
- Add Ukrainian translation (thanks to Yurii Denys)
- Fix empty force_key_frames value
- Fix Icecast publication service
- Fix imprint, terms and credit without share ([#525](https://github.com/datarhei/restreamer/issues/529))
- Fix proxy error on the playersite ([#525](https://github.com/datarhei/restreamer/issues/525))
- Fix saving RTMP advanced options ([#518](https://github.com/datarhei/restreamer/issues/518))
- Fix help buttons for other languages than English and German ([#24](https://github.com/datarhei/restreamer-ui/issues/24))
- Fix internal player skin (volume bar)
- Fix security hints (npm dep.)
## v1.6.0 > v1.7.0
- Add analyzeduration, probesize and max_probe_packets input options
- Add avoid_negative_ts input option
- Add http_proxy input option ([#513](https://github.com/datarhei/restreamer/issues/513))
- Add copyts, start_at_zero and use_wallclock_as_timestamps input options
- Add heuristic to find core address if UI is proxied
- Add Turkish translation (thanks to Ramazan Sancar) ([#22](https://github.com/datarhei/restreamer-ui/issues/22))
- Add Danish translation (Thanks to Filip Stadler and Info)
- Add Slovenian translation (thanks to Grega)
- Add Greek translation
- Mod allows general input settings for pull and push streams
- Mod updates npm dependencies
- Fix Creative Commons icons
- Fix positioning of the deinterlacing filter ([#465](https://github.com/datarhei/restreamer/issues/465))
## v1.5.1 > v1.6.0
- Add Bob Weaver Deinterlacing Filter ([#465](https://github.com/datarhei/restreamer/issues/465))
- Add tests for wizard, network source, and coders
- Add Korean translation (thanks to Jihaeng)
- Mod splitting wizard in components
- Fix wrong call to encoder defaults ([#467](https://github.com/datarhei/restreamer/issues/467))
## v1.5.0 > v1.5.1
- Fix FFmpeg version check for RTSP sources ([#455](https://github.com/datarhei/restreamer/issues/455))
- Fix requires Core >= v16.11.0 and FFmpeg >= 5.1.0
## v1.4.0 > v1.5.0
- Add changelog viewer
- Add skills props to encoder and decoder components
- Add fps_mode to x264, x265, vp9 encoder
- Add scale filter to non-hwaccel encoders
- Add PeerTube and Media Network to publication services (plattforms, software)
- Add reset button to hide a player logo ([#431](https://github.com/datarhei/restreamer/issues/431))
- Mod expands V4L2_M2M options (an unstable RPI 64bit encoder)
- Mod indicates a faulty cache configuration
- Mod switches to the improved SRT syntax (thx to SA Consulting)
- Mod improves display of progress data
- Mod removes deprecated param ocl - now ochl (ff5)
- Mod simplifies the setup of Restreamer-to-Restreamer connections
- Mod adds Istafeed.me as StreamKey service to Instagram's publishing service
- Mod renames "Low delay" to "Low latency (buffer)" and set false as default (requires more feedback)
- Del removes support for clappr player
- Fix npm dependencies (security fixes)
- Fix videojs-overlay logo size ([#431](https://github.com/datarhei/restreamer/issues/431))
- Fix use of TLS for input from local RTMP server
- Fix Icecast publication service settings (datarhei/restreamer#429)
- Fix removes SRT bitstream on tee (OBS > RTMP > SRT is faulty)
Dependency:
- datarhei Core v16.11.0+
## v1.3.0 > v1.4.0
- Add email field for Let's Encrypt certification
Dependency:
- datarhei Core v16.10.1+
## v1.2.0 > v1.3.0
- Add dlive & Trovo publication services
- Add low_delay option to processing (default: true)
- Mod uses the ingest stream for publication (datarhei/restreamer#411)
- Mod optimized DVR on DiskFS
- Mod updates packages
- Fix SRT bitstream on tee
- Fix typo
- Fix viewer count (datarhei/restreamer#394)
- Fix user registration if username and/or password are set via environment (datarhei/restreamer-ui#13)
- Fix Dockerfile, Reduce size, serve production build (datarhei/restreamer-ui#12)
Dependency:
- datarhei Core v16.10.0+
## v1.1.0 > v1.2.0
- Add allow writing HLS to disk
- Add audio pan filter
@ -10,7 +174,7 @@
- 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
@ -21,8 +185,8 @@
- 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
- Fix player problem with different stream formats (9:16)
- Mod allow trailing slash on Core address
- Fix player problem with different stream formats (9:16)
- Fix process report naming
- Fix publication service icon styles
- Fix VAAPI encoder
@ -31,7 +195,7 @@ Dependency:
- datarhei Core v16.9.0+
#### v1.0.0 > v1.1.0
## v1.0.0 > v1.1.0
- Add compatibility list for encoders
- Add "HLS cleanup" as an optional function ([Philipp Trenz](https://github.com/philipptrenz))

6
Caddyfile Normal file
View File

@ -0,0 +1,6 @@
:3000
encode zstd gzip
file_server {
root ./build
}

View File

@ -1,23 +1,25 @@
FROM node:17.9.0-alpine3.15
ARG NODE_IMAGE=node:21-alpine3.20
ARG CADDY_IMAGE=caddy:2.8.4-alpine
ARG NODE_SPACE_SIZE=10240
ENV NODE_OPTIONS="--openssl-legacy-provider --max-old-space-size=$NODE_SPACE_SIZE"
FROM $NODE_IMAGE AS builder
ARG PUBLIC_URL "/"
ENV PUBLIC_URL="./"
COPY . /ui
WORKDIR /ui
RUN cd /ui && \
npm config set fetch-retries 10 && \
npm config set fetch-retry-mintimeout 100000 && \
npm config set fetch-retry-maxtimeout 600000 && \
npm config set cache-min 3600 && \
npm config ls -l && \
npm install && \
npm run build
yarn install && \
yarn build
FROM $CADDY_IMAGE
COPY --from=builder /ui/build /ui/build
COPY --from=builder /ui/Caddyfile /ui/Caddyfile
WORKDIR /ui
EXPOSE 3000
CMD [ "npm", "run", "start" ]
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]

13
Dockerfile.workflow Normal file
View File

@ -0,0 +1,13 @@
ARG CADDY_IMAGE=caddy:2.7.5-alpine
FROM $CADDY_IMAGE
COPY build /ui/build
COPY Caddyfile /ui/Caddyfile
ENV PUBLIC_URL="./"
WORKDIR /ui
EXPOSE 3000
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]

View File

@ -26,23 +26,5 @@ $ npm run i18n-extract:clean
$ npm run i18n-compile
```
### Known outdated dependencies
Requires MUI 5.2+ & React 18 compatibility. Clappr-Player upgrade (or removal).
```sh
@mui/material 5.1.1 → 5.9.0
@mui/styles ^5.1.1 → ^5.9.0
@testing-library/dom ^8.13.0 → ^8.16.0
@testing-library/jest-dom ^4.2.4 → ^5.16.4
@testing-library/react ^12.1.5 → ^13.3.0
@testing-library/user-event ^13.5.0 → ^14.2.5
eslint ^7.32.0 → ^8.19.0
hls.js ^0.14.17 → ^1.1.5
react ^17.0.2 → ^18.2.0
react-dom ^17.0.2 → ^18.2.0
react-scripts ^4.0.3 → ^5.0.1
typescript ^3.9.7 → ^4.7.4
```
## License
See the [LICENSE](./LICENSE) file for licensing information.

View File

@ -1,57 +1,61 @@
{
"name": "restreamer-ui",
"version": "1.2.0",
"bundle": "restreamer-v2.x.x",
"version": "1.14.0",
"bundle": "restreamer-v2.12.0",
"private": false,
"license": "Apache-2.0",
"dependencies": {
"@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",
"@auth0/auth0-spa-js": "^2.1.3",
"@babel/plugin-syntax-flow": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@fontsource/dosis": "^5.0.21",
"@fontsource/roboto": "^5.0.14",
"@fortawesome/fontawesome-svg-core": "^6.6.0",
"@fortawesome/free-brands-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@lingui/core": "^4.11.4",
"@lingui/macro": "^4.11.4",
"@lingui/react": "^4.11.4",
"@mui/icons-material": "^6.0.1",
"@mui/lab": "^6.0.0-beta.8",
"@mui/material": "^6.0.1",
"@mui/styles": "^6.0.1",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/react": "^18.3.5",
"babel-plugin-macros": "^3.1.0",
"eslint": "^7.32.0",
"handlebars": "^4.7.7",
"hls.js": "^0.14.17",
"jwt-decode": "^3.1.2",
"make-plural": "^7.1.0",
"react": "^17.0.2",
"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",
"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",
"url-parse": "^1.5.10",
"uuid": "^8.3.2",
"video.js": "^7.19.2",
"videojs-overlay": "^2.1.5"
"util": "^0.12.5",
"uuid": "^10.0.0",
"video.js": "^8.17.3",
"videojs-overlay": "^3.1.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts --optimize-for-size build",
"start-build": "serve -s build",
"test": "react-scripts test",
"test-ci": "react-scripts test --watchAll=false --testTimeout 50000",
"test-coverage": "react-scripts test --watchAll=false --testTimeout 50000 --coverage",
"eject": "react-scripts eject",
"i18n-extract": "lingui extract",
"i18n-extract:clean": "lingui extract --clean",
@ -73,7 +77,7 @@
},
"browserslist": {
"production": [
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11, maintained node versions"
"> 0.5%, last 2 versions, Firefox ESR, not dead, not IE 11"
],
"development": [
"last 1 chrome version",
@ -82,14 +86,13 @@
]
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@lingui/cli": "^3.14.0",
"babel-core": "^7.0.0-bridge.0",
"prettier": "^2.7.1",
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@lingui/cli": "^4.11.4",
"babel-core": "^6.26.3",
"eslint-config-react-app": "^7.0.1",
"prettier": "^3.3.3",
"react-error-overlay": "^6.0.11"
},
"resolutions": {
"url-parse@1.5.3": "patch:url-parse@npm:1.5.3#.yarn/patches/url-parse-npm-1.5.3-225ab9cae7.patch",
"react-error-overlay": "6.0.9"
}
}
"resolutions": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +0,0 @@
dist/clappr.min.js.map
dist/clappr.min.js
dist/clappr-stats.min.js
dist/clappr-nerd-stats.min.js

View File

@ -1,112 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="{{description}}">
<meta name="author" content="datarhei restreamer">
<title>{{name}}</title>
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
<link rel="alternate" type="application/json+oembed" href="channels/{{channelid}}/oembed.json" title="{{name}}">
<link rel="alternate" type="text/xml+oembed" href="channels/{{channelid}}/oembed.xml" title="{{name}}">
<script src="channels/{{channelid}}/config.js"></script>
<script src="player/clappr/dist/clappr.min.js"></script>
<script src="player/clappr/dist/clappr-stats.min.js"></script>
<script src="player/clappr/dist/clappr-nerd-stats.min.js"></script>
<style>
.player-poster[data-poster] .poster-background[data-poster] {
height: initial !important;
}
</style>
</head>
<body>
<div id="player" style="position:absolute;top:0;right:0;bottom:0;left:0"></div>
<script>
function getQueryParam(key, defaultValue) {
var query = window.location.search.substring(1);
var vars = query.split("&");
for(var i = 0; i < vars.length; i++) {
var pair = vars[i].split("=");
if(pair[0] == key) {
return pair[1];
}
}
return defaultValue;
}
function convertBoolParam(key, defaultValue) {
var val = getQueryParam(key, defaultValue);
return val === true || val === "true" || val === "1" || val === "yes" || val === "on";
}
function convertColorParam(parameter, defaultColor) {
var re = new RegExp("^#([0-9a-f]{3}|[0-9a-f]{6})$");
var c = getQueryParam(parameter, defaultColor);
// decode color as # has to be represented by %23
var c = decodeURIComponent(c);
// if color was given without leading #, prepend it
if (!String(c).startsWith("#")) c = "#" + c;
if (re.test(c)) {
return c;
} else {
return defaultColor;
}
}
var autoplay = convertBoolParam("autoplay", playerConfig.autoplay);
var mute = convertBoolParam("mute", playerConfig.mute);
var statistics = convertBoolParam("stats", playerConfig.statistics);
var color = convertColorParam("color", playerConfig.color.buttons);
var plugins = [];
if(statistics == true) {
plugins.push(ClapprNerdStats);
plugins.push(ClapprStats);
}
var config = {
source: playerConfig.source,
parentId: '#player',
baseUrl: 'clappr/',
plugins: plugins,
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
mediacontrol: {
seekbar: playerConfig.color.seekbar,
buttons: color
},
height: '100%',
width: '100%',
disableCanAutoPlay: true,
autoPlay: autoplay,
mute: mute,
clapprStats: {
runEach: 1000,
onReport: (metrics) => {},
},
clapprNerdStats: {
shortcut: ['command+shift+s', 'ctrl+shift+s'],
iconPosition: 'top-right'
}
};
if(playerConfig.logo.image.length != 0) {
config.watermark = playerConfig.logo.image;
config.position = playerConfig.logo.position;
if(playerConfig.logo.link.length != 0) {
config.watermarkLink = playerConfig.logo.link;
}
}
var player = new window.Clappr.Player(config);
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster () {
posterPlugin.options.poster = playerConfig.poster + '?t=' + String(new Date().getTime());
posterPlugin.render();
});
</script>
</body>
</html>

View File

@ -95,7 +95,7 @@
/* volume-panel */
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
padding-top: 1em;
padding-top: .9em;
}
.vjs-public .vjs-control .vjs-volume-panel {
@ -110,8 +110,8 @@
/* disable caps */
.vjs-internal .vjs-subs-caps-button {
display: none;
.vjs-public .vjs-subs-caps-button {
display: none!important;
}
/* spacer */
@ -123,33 +123,37 @@
/* overlay */
.vjs-public .vjs-overlay > a > img {
width: 100%;
.vjs-public .vjs-overlay-no-background {
max-width: 25%!important;
}
.vjs-public .vjs-overlay-no-background {
max-width: 28%!important;
max-height: 28%!important;
.vjs-public .vjs-overlay-no-background > img, .vjs-public .vjs-overlay-no-background > a > img {
max-width: 100%!important;
height: auto!important;
}
.vjs-public .vjs-overlay-top-left {
top: 20px!important;
left: 30px!important;
top: 15px!important;
left: 20px!important;
text-align: right;
}
.vjs-public .vjs-overlay-top-right {
top: 20px!important;
right: 30px!important;
top: 15px!important;
right: 20px!important;
text-align: right;
}
.vjs-public .vjs-overlay-bottom-left {
bottom: 20px!important;
left: 30px!important;
bottom: 15px!important;
left: 20px!important;
text-align: left;
}
.vjs-public .vjs-overlay-bottom-right {
bottom: 20px!important;
right: 30px!important;
bottom: 15px!important;
right: 20px!important;
text-align: left;
}
/* context menu */
@ -166,4 +170,3 @@
.vjs-public .vjs-lock-open {
z-index: 1000;
}

View File

@ -1 +1 @@
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public.vjs-big-play-button:focus,.vjs-public:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:0 0;background-image:linear-gradient(0deg,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay>a>img{width:100%}.vjs-public .vjs-overlay-no-background{max-width:28%!important;max-height:28%!important}.vjs-public .vjs-overlay-top-left{top:20px!important;left:30px!important}.vjs-public .vjs-overlay-top-right{top:20px!important;right:30px!important}.vjs-public .vjs-overlay-bottom-left{bottom:20px!important;left:30px!important}.vjs-public .vjs-overlay-bottom-right{bottom:20px!important;right:30px!important}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:none;line-height:180px;font-size:180px;border:0;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public:hover .vjs-big-play-button,.vjs-public.vjs-big-play-button:focus{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:none;background-image:linear-gradient(0,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:.9em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-public .vjs-subs-caps-button{display:none!important}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay-no-background{max-width:25%!important}.vjs-public .vjs-overlay-no-background>img,.vjs-public .vjs-overlay-no-background>a>img{max-width:100%!important;height:auto!important}.vjs-public .vjs-overlay-top-left{top:15px!important;left:20px!important;text-align:right}.vjs-public .vjs-overlay-top-right{top:15px!important;right:20px!important;text-align:right}.vjs-public .vjs-overlay-bottom-left{bottom:15px!important;left:20px!important;text-align:left}.vjs-public .vjs-overlay-bottom-right{bottom:15px!important;right:20px!important;text-align:left}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-airplay-button-label{flex-grow:1;margin-left:4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}

View File

@ -0,0 +1,308 @@
(function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
"use strict";
var hasAirPlayAPISupport = require('../lib/hasAirPlayAPISupport');
/**
* Registers the AirPlayButton Component with Video.js. Calls
* {@link http://docs.videojs.com/Component.html#.registerComponent}, which will add a
* component called `airPlayButton` to the list of globally registered Video.js
* components. The `airPlayButton` is added to the player's control bar UI automatically
* once {@link module:enableAirPlay} has been called. If you would like to specify the
* order of the buttons that appear in the control bar, including this button, you can do
* so in the options that you pass to the `videojs` function when creating a player:
*
* ```
* videojs('playerID', {
* controlBar: {
* children: [
* 'playToggle',
* 'progressControl',
* 'volumePanel',
* 'fullscreenToggle',
* 'airPlayButton',
* ],
* }
* });
* ```
*
* @param videojs {object} A reference to {@link http://docs.videojs.com/module-videojs.html|Video.js}
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin
*/
module.exports = function (videojs) {
/**
* The AirPlayButton module contains both the AirPlayButton class definition and the
* function used to register the button as a Video.js Component.
*
* @module AirPlayButton
*/
const ButtonComponent = videojs.getComponent('Button');
/**
* The Video.js Button class is the base class for UI button components.
*
* @external Button
* @see {@link http://docs.videojs.com/Button.html|Button}
*/
/** @lends AirPlayButton.prototype */
class AirPlayButton extends ButtonComponent {
/**
* This class is a button component designed to be displayed in the
* player UI's control bar. It displays an Apple AirPlay selection
* list when clicked.
*
* @constructs
* @extends external:Button
*/
constructor(player, options) {
super(player, options);
if (!hasAirPlayAPISupport()) {
this.hide();
}
this._reactToAirPlayAvailableEvents();
if (options.addAirPlayLabelToButton) {
this.el().classList.add('vjs-airplay-button-lg');
this._labelEl = document.createElement('span');
this._labelEl.classList.add('vjs-airplay-button-label');
this._labelEl.textContent = this.localize('AirPlay');
this.el().appendChild(this._labelEl);
} else {
this.controlText('Start AirPlay');
}
}
/**
* Overrides Button#buildCSSClass to return the classes used on the button element.
*
* @param {DOMElement} el
* @see {@link http://docs.videojs.com/Button.html#buildCSSClass|Button#buildCSSClass}
*/
buildCSSClass() {
return 'vjs-airplay-button ' + super.buildCSSClass();
}
/**
* Overrides Button#handleClick to handle button click events. AirPlay
* functionality is handled outside of this class, which should be limited
* to UI related logic. This function simply triggers an event on the player.
*
* @fires AirPlayButton#airPlayRequested
* @param {DOMElement} el
* @see {@link http://docs.videojs.com/Button.html#handleClick|Button#handleClick}
*/
handleClick() {
this.player().trigger('airPlayRequested');
}
/**
* Gets the underlying DOMElement used by the player.
*
* @private
* @returns {DOMElement} either an <audio> or <video> tag, depending on the type of
* player
*/
_getMediaEl() {
var playerEl = this.player().el();
return playerEl.querySelector('video, audio');
}
/**
* Binds a listener to the `webkitplaybacktargetavailabilitychanged` event, if it is
* supported, that will show or hide this button Component based on the availability
* of the AirPlay function.
*
* @private
*/
_reactToAirPlayAvailableEvents() {
var mediaEl = this._getMediaEl(),
self = this;
if (!mediaEl || !hasAirPlayAPISupport()) {
return;
}
function onTargetAvailabilityChanged(event) {
if (event.availability === 'available') {
self.show();
} else {
self.hide();
}
}
mediaEl.addEventListener('webkitplaybacktargetavailabilitychanged', onTargetAvailabilityChanged);
this.on('dispose', function () {
mediaEl.removeEventListener('webkitplaybacktargetavailabilitychanged', onTargetAvailabilityChanged);
});
}
}
videojs.registerComponent('airPlayButton', AirPlayButton);
};
},{"../lib/hasAirPlayAPISupport":4}],2:[function(require,module,exports){
"use strict";
/**
* @module enableAirPlay
*/
var hasAirPlayAPISupport = require('./lib/hasAirPlayAPISupport');
/**
* @private
* @param {object} the Video.js Player instance
* @returns {AirPlayButton} or `undefined` if it does not exist
*/
function getExistingAirPlayButton(player) {
return player.controlBar.getChild('airPlayButton');
}
/**
* Adds the AirPlayButton Component to the player's ControlBar component, if the
* AirPlayButton does not already exist in the ControlBar.
* @private
* @param player {object} the Video.js Player instance
* @param options {object}
*/
function ensureAirPlayButtonExists(player, options) {
var existingAirPlayButton = getExistingAirPlayButton(player),
indexOpt;
if (options.addButtonToControlBar && !existingAirPlayButton) {
// Figure out AirPlay button's index
indexOpt = player.controlBar.children().length;
if (typeof options.buttonPositionIndex !== 'undefined') {
indexOpt = options.buttonPositionIndex >= 0 ? options.buttonPositionIndex : player.controlBar.children().length + options.buttonPositionIndex;
}
player.controlBar.addChild('airPlayButton', options, indexOpt);
}
}
/**
* Handles requests for AirPlay triggered by the AirPlayButton Component.
*
* @private
* @param player {object} the Video.js Player instance
*/
function onAirPlayRequested(player) {
var mediaEl = player.el().querySelector('video, audio');
if (mediaEl && mediaEl.webkitShowPlaybackTargetPicker) {
mediaEl.webkitShowPlaybackTargetPicker();
}
}
/**
* Adds an event listener for the `airPlayRequested` event triggered by the AirPlayButton
* Component.
*
* @private
* @param player {object} the Video.js Player instance
*/
function listenForAirPlayEvents(player) {
// Respond to requests for AirPlay. The AirPlayButton component triggers this event
// when the user clicks the AirPlay button.
player.on('airPlayRequested', onAirPlayRequested.bind(null, player));
}
/**
* Sets up the AirPlay plugin.
*
* @private
* @param player {object} the Video.js player
* @param options {object} the plugin options
*/
function enableAirPlay(player, options) {
if (!player.controlBar) {
return;
}
if (hasAirPlayAPISupport()) {
listenForAirPlayEvents(player);
ensureAirPlayButtonExists(player, options);
}
}
/**
* Registers the AirPlay plugin with Video.js. Calls
* {@link http://docs.videojs.com/module-videojs.html#~registerPlugin|videojs#registerPlugin},
* which will add a plugin function called `airPlay` to any instance of a Video.js player
* that is created after calling this function. Call `player.airPlay(options)`, passing in
* configuration options, to enable the AirPlay plugin on your Player instance.
*
* Currently, the only configuration option is:
*
* * **buttonText** - the text to display inside of the button component. By default,
* this text is hidden and is used for accessibility purposes.
*
* @param {object} videojs
* @see http://docs.videojs.com/module-videojs.html#~registerPlugin
*/
module.exports = function (videojs) {
videojs.registerPlugin('airPlay', function (options) {
var pluginOptions = Object.assign({
addButtonToControlBar: true
}, options || {});
// `this` is an instance of a Video.js Player.
// Wait until the player is "ready" so that the player's control bar component has
// been created.
this.ready(enableAirPlay.bind(this, this, pluginOptions));
});
};
},{"./lib/hasAirPlayAPISupport":4}],3:[function(require,module,exports){
"use strict";
var createAirPlayButton = require('./components/AirPlayButton'),
createAirPlayPlugin = require('./enableAirPlay');
/**
* @module index
*/
/**
* Registers the AirPlay plugin and AirPlayButton Component with Video.js. See
* {@link module:AirPlayButton} and {@link module:enableAirPlay} for more details about
* how the plugin and button are registered and configured.
*
* @param {object} videojs
* @see module:enableAirPlay
* @see module:AirPlayButton
*/
module.exports = function (videojs) {
videojs = videojs || window.videojs;
createAirPlayButton(videojs);
createAirPlayPlugin(videojs);
};
},{"./components/AirPlayButton":1,"./enableAirPlay":2}],4:[function(require,module,exports){
"use strict";
/**
* @module hasAirPlayAPISupport
*/
/**
* Returns whether or not the current browser environment supports AirPlay.
*
* @private
* @returns {boolean} true if AirPlay support is available
*/
module.exports = function () {
return !!window.WebKitPlaybackTargetAvailabilityEvent;
};
},{}],5:[function(require,module,exports){
"use strict";
/**
* This module is used as an entry point for the build system to bundle this plugin into a
* single javascript file that can be loaded by a script tag on a web page. The javascript
* file that is built assumes that `videojs` is available globally at `window.videojs`, so
* Video.js must be loaded **before** this plugin is loaded.
*
* Run `npm install` and then `grunt build` to build the plugin's bundled javascript
* file, as well as the CSS and image assets into the project's `./dist/` folder.
*
* @module standalone
*/
require('./index')();
},{"./index":3}]},{},[5]);

View File

@ -1 +1 @@
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:20px;height:20px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}
.vjs-airplay-button .vjs-icon-placeholder{background:url("ic_airplay_white_24px.svg") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-airplay-button:hover{cursor:pointer}.vjs-airplay-button:hover .vjs-icon-placeholder{background-image:url("ic_airplay_white_24px.svg")}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-airplay-button-label{flex-grow:1;margin-left:4px}.vjs-airplay-button.vjs-airplay-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}

File diff suppressed because it is too large Load Diff

View File

@ -1,65 +1 @@
/** Silvermine Chromecast **/
.vjs-chromecast-button .vjs-icon-placeholder {
background: url('ic_cast_white_24dp.png') center center no-repeat;
background-size: contain;
display: inline-block;
width: 20px;
height: 20px;
}
.vjs-chromecast-button:hover {
cursor: pointer;
}
.vjs-chromecast-button:hover .vjs-icon-placeholder {
background-image: url('ic_cast_white_24dp.png');
}
.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder {
background-image: url('ic_cast_connected_white_24dp.png');
}
.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder {
background-image: url('ic_cast_connected_white_24dp.png');
}
.vjs-tech-chromecast {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
overflow: hidden;
}
.vjs-tech-chromecast .vjs-tech-chromecast-poster::after {
content: ' ';
display: block;
height: 2px;
width: 100px;
background-color: #cccccc;
position: absolute;
left: calc(50% - 50px);
}
.vjs-tech-chromecast .vjs-tech-chromecast-poster-img {
max-height: 180px;
width: auto;
border: 2px solid #cccccc;
}
.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty {
width: 160px;
height: 90px;
}
.vjs-tech-chromecast .vjs-tech-chromecast-title-container {
position: absolute;
bottom: 50%;
margin-bottom: 100px;
color: #cccccc;
text-align: center;
}
.vjs-tech-chromecast .vjs-tech-chromecast-title {
font-size: 22px;
}
.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty {
display: none;
}
.vjs-tech-chromecast .vjs-tech-chromecast-subtitle {
font-size: 18px;
padding-top: 0.5em;
}
.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty {
display: none;
}
.vjs-chromecast-button .vjs-icon-placeholder{background:url("ic_cast_white_24dp.png") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url("ic_cast_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-chromecast-button-label{flex-grow:1;margin-left:4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:" ";display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}

File diff suppressed because it is too large Load Diff

View File

@ -1 +1 @@
.vjs-chromecast-button .vjs-icon-placeholder{background:url(ic_cast_white_24dp.png) center center no-repeat;background-size:contain;display:inline-block;width:20px;height:20px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url(ic_cast_white_24dp.png)}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url(ic_cast_connected_white_24dp.png)}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url(ic_cast_connected_white_24dp.png)}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:' ';display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}
.vjs-chromecast-button .vjs-icon-placeholder{background:url("ic_cast_white_24dp.png") center center no-repeat;background-size:contain;display:inline-block;width:12px;height:12px}.vjs-chromecast-button:hover{cursor:pointer}.vjs-chromecast-button:hover .vjs-icon-placeholder{background-image:url("ic_cast_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-casting-state:hover .vjs-icon-placeholder{background-image:url("ic_cast_connected_white_24dp.png")}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden){display:flex;align-items:center;width:auto;padding:0 4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-chromecast-button-label{flex-grow:1;margin-left:4px}.vjs-chromecast-button.vjs-chromecast-button-lg:not(.vjs-hidden) .vjs-icon-placeholder{flex-grow:1}.vjs-tech-chromecast{display:flex;flex-direction:column;justify-content:center;align-items:center;overflow:hidden}.vjs-tech-chromecast .vjs-tech-chromecast-poster::after{content:" ";display:block;height:2px;width:100px;background-color:#ccc;position:absolute;left:calc(50% - 50px)}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img{max-height:180px;width:auto;border:2px solid #ccc}.vjs-tech-chromecast .vjs-tech-chromecast-poster-img.vjs-tech-chromecast-poster-img-empty{width:160px;height:90px}.vjs-tech-chromecast .vjs-tech-chromecast-title-container{position:absolute;bottom:50%;margin-bottom:100px;color:#ccc;text-align:center}.vjs-tech-chromecast .vjs-tech-chromecast-title{font-size:22px}.vjs-tech-chromecast .vjs-tech-chromecast-title.vjs-tech-chromecast-title-empty{display:none}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle{font-size:18px;padding-top:.5em}.vjs-tech-chromecast .vjs-tech-chromecast-subtitle.vjs-tech-chromecast-subtitle-empty{display:none}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,459 +1,340 @@
/*! @name videojs-license @version 0.1.0 @license MIT */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js'), require('global/document')) :
typeof define === 'function' && define.amd ? define(['video.js', 'global/document'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs, global.document));
}(this, (function (videojs, document) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
var document__default = /*#__PURE__*/_interopDefaultLegacy(document);
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var assertThisInitialized = createCommonjsModule(function (module) {
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
module.exports = _assertThisInitialized;
module.exports["default"] = module.exports, module.exports.__esModule = true;
});
var setPrototypeOf = createCommonjsModule(function (module) {
function _setPrototypeOf(o, p) {
module.exports = _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
module.exports["default"] = module.exports, module.exports.__esModule = true;
return _setPrototypeOf(o, p);
}
module.exports = _setPrototypeOf;
module.exports["default"] = module.exports, module.exports.__esModule = true;
});
var inheritsLoose = createCommonjsModule(function (module) {
function _inheritsLoose(subClass, superClass) {
subClass.prototype = Object.create(superClass.prototype);
subClass.prototype.constructor = subClass;
setPrototypeOf(subClass, superClass);
}
module.exports = _inheritsLoose;
module.exports["default"] = module.exports, module.exports.__esModule = true;
});
var version = "0.1.0";
var Plugin = videojs__default['default'].getPlugin('plugin');
var Component = videojs__default['default'].getComponent('Component');
var Button = videojs__default['default'].getComponent('MenuButton'); // Default options for the plugin.
var defaults = {
license: 'none',
title: '',
author: '',
languages: {
license: 'License',
loading: 'Loading'
}
};
/**
* An advanced Video.js plugin. For more information on the API
*
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
*/
var License = /*#__PURE__*/function (_Plugin) {
inheritsLoose(License, _Plugin);
/**
* Create a License plugin instance.
*
* @param {Player} player
* A Video.js Player instance.
*
* @param {Object} [options]
* An optional options object.
*
* While not a core part of the Video.js plugin architecture, a
* second argument of options is a convenient way to accept inputs
* from your plugin's caller.
*/
function License(player, options) {
var _this;
// the parent class will add player under this.player
_this = _Plugin.call(this, player) || this;
_this.playerId = _this.player.id();
_this.options = videojs__default['default'].mergeOptions(defaults, options);
if (options.license === 'none') {
return assertThisInitialized(_this);
}
_this.player.ready(function () {
_this.player.addClass('vjs-license');
_this.buildUI();
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
_this.mobileBuildUI();
}
}); // close the menu if open on userinactive
_this.player.on('userinactive', function () {
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
element.classList.remove('vjs-lock-open');
});
}); // close the menu if anywhere in the player is clicked
_this.player.on('click', function (evt) {
if (evt.target.tagName === 'VIDEO') {
document__default['default'].getElementById(_this.playerId).querySelectorAll('.vjs-menu').forEach(function (element) {
element.classList.remove('vjs-lock-open');
});
}
});
_this.player.on('loadstart', function (_event) {
_this.removeElementsByClass('vjs-license-clear');
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
_this.mobileBuildTopLevelMenu();
} else {
_this.buildTopLevelMenu();
}
});
return _this;
}
/**
* Add the menu ui button to the controlbar
*/
var _proto = License.prototype;
_proto.buildUI = function buildUI() {
var playerId = this.playerId;
var that = this;
/**
* LicenseMenuButton
*/
var LicenseMenuButton = /*#__PURE__*/function (_Button) {
inheritsLoose(LicenseMenuButton, _Button);
/**
* Contructor
*
* @param {*} player videojs player instance
* @param {*} options videojs player options
*/
function LicenseMenuButton(player, options) {
var _this2;
_this2 = _Button.call(this, player, options) || this;
_this2.addClass('vjs-license');
_this2.controlText(that.options.languages.loading);
player.one('canplaythrough', function (_event) {
_this2.controlText(that.options.languages.settings);
});
_this2.menu.contentEl_.id = playerId + '-vjs-license-default';
return _this2;
}
/**
* Handle click
*/
var _proto2 = LicenseMenuButton.prototype;
_proto2.handleClick = function handleClick() {
if (videojs__default['default'].browser.IS_IOS || videojs__default['default'].browser.IS_ANDROID) {
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
} else {
this.el().classList.toggle('vjs-toogle-btn');
this.menu.el().classList.toggle('vjs-lock-open');
}
};
return LicenseMenuButton;
}(Button);
videojs__default['default'].registerComponent('licenseMenuButton', LicenseMenuButton);
this.player.getChild('controlBar').addChild('licenseMenuButton');
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
this.player.getChild('controlBar').el().insertBefore(this.player.getChild('controlBar').getChild('licenseMenuButton').el(), this.player.getChild('controlBar').getChild('fullscreenToggle').el());
}
}
/**
*
* This is just build the top level menu no sub menus
*/
;
_proto.buildTopLevelMenu = function buildTopLevelMenu() {
var settingsButton = this.player.getChild('controlBar').getChild('licenseMenuButton'); // settingsButton.addClass('vjs-license-is-loaded');
var main = settingsButton.menu.contentEl_; // Empty the main menu div to repopulate
main.innerHTML = '';
main.classList.add('vjs-license-top-level'); // Start building new list items
var menuTitle = document__default['default'].createElement('li');
menuTitle.className = 'vjs-license-top-level-header';
var menuTitleInner = document__default['default'].createElement('span');
menuTitleInner.innerHTML = 'About';
menuTitleInner.className = 'vjs-license-top-level-header-titel';
menuTitle.appendChild(menuTitleInner);
main.appendChild(menuTitle);
var itemTitel = document__default['default'].createElement('li');
itemTitel.innerHTML = this.buildItemTitel();
itemTitel.className = 'vjs-license-top-level-item';
main.appendChild(itemTitel);
if (this.options.author) {
var itemAuthor = document__default['default'].createElement('li');
itemAuthor.innerHTML = this.buildItemAuthor();
itemAuthor.className = 'vjs-license-top-level-item';
main.appendChild(itemAuthor);
}
var itemLicense = document__default['default'].createElement('li');
itemLicense.innerHTML = this.buildItemLicense();
itemLicense.className = 'vjs-license-top-level-item';
main.appendChild(itemLicense);
}
/**
* Add the menu ui button to the controlbar
*/
;
_proto.mobileBuildUI = function mobileBuildUI() {
/**
* bla
*/
var LicenseMenuMobileModal = /*#__PURE__*/function (_Component) {
inheritsLoose(LicenseMenuMobileModal, _Component);
/**
* Contructor
*
* @param {*} player videojs player instance
* @param {*} options videojs player options
*/
function LicenseMenuMobileModal(player, options) {
return _Component.call(this, player, options) || this;
}
/**
* Creates an HTML element
*
* @return {Object} HTML element
*/
var _proto3 = LicenseMenuMobileModal.prototype;
_proto3.createEl = function createEl() {
return videojs__default['default'].createEl('div', {
className: 'vjs-license-mobile'
});
};
return LicenseMenuMobileModal;
}(Component);
videojs__default['default'].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
videojs__default['default'].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document__default['default'].body);
}
/**
* Add the menu ui button to the controlbar
*/
;
_proto.mobileBuildTopLevelMenu = function mobileBuildTopLevelMenu() {
var _this3 = this;
var settingsButton = this.player.getChild('licenseMenuMobileModal');
var menuTopLevel = document__default['default'].createElement('ul');
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
settingsButton.el().appendChild(menuTopLevel); // Empty the main menu div to repopulate
var menuTitle = document__default['default'].createElement('li');
menuTitle.innerHTML = 'About';
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
menuTopLevel.appendChild(menuTitle);
var itemTitel = document__default['default'].createElement('li');
itemTitel.innerHTML = this.buildItemTitel();
itemTitel.className = 'vjs-license-top-level-item';
if (this.options.author) {
var itemAuthor = document__default['default'].createElement('li');
itemAuthor.innerHTML = this.buildItemAuthor();
itemAuthor.className = 'vjs-license-top-level-item';
}
var itemLicense = document__default['default'].createElement('li');
itemLicense.innerHTML = this.buildItemLicense();
itemLicense.className = 'vjs-license-top-level-item';
var menuClose = document__default['default'].createElement('li');
menuClose.innerHTML = 'Close';
menuClose.className = 'setting-menu-footer-default';
menuClose.onclick = function (e) {
_this3.player.getChild('settingsMenuMobileModal').el().style.display = 'none';
};
menuTopLevel.appendChild(menuClose);
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
;
_proto.buildItemTitel = function buildItemTitel() {
var titel = '';
if (this.options.title) {
titel = "" + this.options.title;
}
return 'Title: ' + titel;
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
;
_proto.buildItemAuthor = function buildItemAuthor() {
var author = '';
if (this.options.author) {
author = " by " + this.options.author;
}
return 'Author: ' + author;
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
;
_proto.buildItemLicense = function buildItemLicense() {
var license = '';
var reVersion = new RegExp('[0-9]+.[0-9]+$');
var version = '4.0';
var matches = this.options.license.match(reVersion);
if (matches !== null) {
version = matches[0];
}
var which = this.options.license.replace(reVersion, '').trim();
var deed = null;
switch (which) {
case 'CC0':
deed = 'https://creativecommons.org/licenses/zero/1.0/';
break;
case 'CC BY':
deed = "https://creativecommons.org/licenses/by/" + version + "/";
break;
case 'CC BY-SA':
deed = "https://creativecommons.org/licenses/by-sa/" + version + "/";
break;
case 'CC BY-NC':
deed = "https://creativecommons.org/licenses/by-nc/" + version + "/";
break;
case 'CC BY-NC-SA':
deed = "https://creativecommons.org/licenses/by-nc-sa/" + version + "/";
break;
case 'CC BY-ND':
deed = "https://creativecommons.org/licenses/by-nd/" + version + "/";
break;
case 'CC BY-NC-ND':
deed = "https://creativecommons.org/licenses/by-nc-nd/" + version + "/";
break;
}
if (deed) {
license = "<a href=\"" + deed + "\" onclick=\"window.open('" + deed + "')\" target=\"_blank\" rel=\"noopener\">" + this.options.license + "</a>";
} else {
license = this.options.license;
}
return 'License: ' + license;
}
/**
*
* Helper class to clear menu items before rebuild
*
* @param {*} className Name of a class
*/
;
_proto.removeElementsByClass = function removeElementsByClass(className) {
// Need to prevent the menu from not showing sometimes
document__default['default'].querySelectorAll('.vjs-sm-top-level').forEach(function (element) {
element.classList.remove('vjs-hidden');
});
var elements = document__default['default'].getElementsByClassName(className);
while (elements.length > 0) {
elements[0].parentNode.removeChild(elements[0]);
}
};
return License;
}(Plugin); // Define default values for the plugin's `state` object here.
License.defaultState = {}; // Include the version number.
License.VERSION = version; // Register the plugin with video.js.
videojs__default['default'].registerPlugin('license', License);
return License;
})));
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('video.js')) :
typeof define === 'function' && define.amd ? define(['video.js'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.videojsLicense = factory(global.videojs));
})(this, (function (videojs) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
var version = "0.1.0";
const Plugin = videojs__default["default"].getPlugin('plugin');
const Component = videojs__default["default"].getComponent('Component');
const Button = videojs__default["default"].getComponent('MenuButton');
// Default options for the plugin.
const defaults = {
license: 'none',
title: '',
author: '',
languages: {
license: 'License',
loading: 'Loading'
}
};
/**
* An advanced Video.js plugin. For more information on the API
*
* See: https://blog.videojs.com/feature-spotlight-advanced-plugins/
*/
class License extends Plugin {
/**
* Create a License plugin instance.
*
* @param {Player} player
* A Video.js Player instance.
*
* @param {Object} [options]
* An optional options object.
*
* While not a core part of the Video.js plugin architecture, a
* second argument of options is a convenient way to accept inputs
* from your plugin's caller.
*/
constructor(player, options) {
// the parent class will add player under this.player
super(player);
this.playerId = this.player.id();
this.options = videojs__default["default"].mergeOptions(defaults, options);
if (options.license === 'none') {
return;
}
player.addClass('vjs-license');
this.buildUI();
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
this.mobileBuildUI();
}
// close the menu if open on userinactive
this.player.on('userinactive', () => {
document.getElementById(this.playerId).querySelectorAll('.vjs-menu').forEach(element => {
element.classList.remove('vjs-lock-open');
});
});
// close the menu if anywhere in the player is clicked
this.player.on('click', evt => {
if (evt.target.tagName === 'VIDEO') {
document.getElementById(this.playerId).querySelectorAll('.vjs-menu').forEach(element => {
element.classList.remove('vjs-lock-open');
});
}
});
this.player.on('loadstart', _event => {
this.removeElementsByClass('vjs-license-clear');
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
this.mobileBuildTopLevelMenu();
} else {
this.buildTopLevelMenu();
}
});
}
/**
* Add the menu ui button to the controlbar
*/
buildUI() {
const playerId = this.playerId;
const that = this;
/**
* LicenseMenuButton
*/
class LicenseMenuButton extends Button {
/**
* Contructor
*
* @param {*} player videojs player instance
* @param {*} options videojs player options
*/
constructor(player, options) {
super(player, options);
this.addClass('vjs-license');
this.controlText(that.options.languages.loading);
player.one('canplaythrough', _event => {
this.controlText(that.options.languages.settings);
});
this.menu.contentEl_.id = playerId + '-vjs-license-default';
}
/**
* Handle click
*/
handleClick() {
if (videojs__default["default"].browser.IS_IOS || videojs__default["default"].browser.IS_ANDROID) {
this.player.getChild('licenseMenuMobileModal').el().style.display = 'block';
} else {
this.el().classList.toggle('vjs-toogle-btn');
this.menu.el().classList.toggle('vjs-lock-open');
}
}
}
videojs__default["default"].registerComponent('licenseMenuButton', LicenseMenuButton);
this.player.getChild('controlBar').addChild('licenseMenuButton');
if (this.player.getChild('controlBar').getChild('fullscreenToggle')) {
this.player.getChild('controlBar').el().insertBefore(this.player.getChild('controlBar').getChild('licenseMenuButton').el(), this.player.getChild('controlBar').getChild('fullscreenToggle').el());
}
}
/**
*
* This is just build the top level menu no sub menus
*/
buildTopLevelMenu() {
const settingsButton = this.player.getChild('controlBar').getChild('licenseMenuButton');
// settingsButton.addClass('vjs-license-is-loaded');
const main = settingsButton.menu.contentEl_;
// Empty the main menu div to repopulate
main.innerHTML = '';
main.classList.add('vjs-license-top-level');
// Start building new list items
const menuTitle = document.createElement('li');
menuTitle.className = 'vjs-license-top-level-header';
const menuTitleInner = document.createElement('span');
menuTitleInner.innerHTML = 'About';
menuTitleInner.className = 'vjs-license-top-level-header-titel';
menuTitle.appendChild(menuTitleInner);
main.appendChild(menuTitle);
const itemTitel = document.createElement('li');
itemTitel.innerHTML = this.buildItemTitel();
itemTitel.className = 'vjs-license-top-level-item';
main.appendChild(itemTitel);
if (this.options.author) {
const itemAuthor = document.createElement('li');
itemAuthor.innerHTML = this.buildItemAuthor();
itemAuthor.className = 'vjs-license-top-level-item';
main.appendChild(itemAuthor);
}
const itemLicense = document.createElement('li');
itemLicense.innerHTML = this.buildItemLicense();
itemLicense.className = 'vjs-license-top-level-item';
main.appendChild(itemLicense);
}
/**
* Add the menu ui button to the controlbar
*/
mobileBuildUI() {
/**
* bla
*/
class LicenseMenuMobileModal extends Component {
/**
* Contructor
*
* @param {*} player videojs player instance
* @param {*} options videojs player options
*/
constructor(player, options) {
super(player, options);
}
/**
* Creates an HTML element
*
* @return {Object} HTML element
*/
createEl() {
return videojs__default["default"].createEl('div', {
className: 'vjs-license-mobile'
});
}
}
videojs__default["default"].registerComponent('licenseMenuMobileModal', LicenseMenuMobileModal);
videojs__default["default"].dom.prependTo(this.player.addChild('licenseMenuMobileModal').el(), document.body);
}
/**
* Add the menu ui button to the controlbar
*/
mobileBuildTopLevelMenu() {
const settingsButton = this.player.getChild('licenseMenuMobileModal');
const menuTopLevel = document.createElement('ul');
menuTopLevel.className = 'vjs-license-mob-top-level vjs-setting-menu-clear';
settingsButton.el().appendChild(menuTopLevel);
// Empty the main menu div to repopulate
const menuTitle = document.createElement('li');
menuTitle.innerHTML = 'About';
menuTitle.className = 'vjs-setting-menu-mobile-top-header';
menuTopLevel.appendChild(menuTitle);
const itemTitel = document.createElement('li');
itemTitel.innerHTML = this.buildItemTitel();
itemTitel.className = 'vjs-license-top-level-item';
if (this.options.author) {
const itemAuthor = document.createElement('li');
itemAuthor.innerHTML = this.buildItemAuthor();
itemAuthor.className = 'vjs-license-top-level-item';
}
const itemLicense = document.createElement('li');
itemLicense.innerHTML = this.buildItemLicense();
itemLicense.className = 'vjs-license-top-level-item';
const menuClose = document.createElement('li');
menuClose.innerHTML = 'Close';
menuClose.className = 'setting-menu-footer-default';
menuClose.onclick = e => {
this.player.getChild('settingsMenuMobileModal').el().style.display = 'none';
};
menuTopLevel.appendChild(menuClose);
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
buildItemTitel() {
let titel = '';
if (this.options.title) {
titel = `${this.options.title}`;
}
return 'Title: ' + titel;
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
buildItemAuthor() {
let author = '';
if (this.options.author) {
author = ` by ${this.options.author}`;
}
return 'Author: ' + author;
}
/**
* Add the menu ui button to the controlbar
*
* @return {string} Returns license text
*/
buildItemLicense() {
let license = '';
const reVersion = new RegExp('[0-9]+.[0-9]+$');
let version = '4.0';
const matches = this.options.license.match(reVersion);
if (matches !== null) {
version = matches[0];
}
const which = this.options.license.replace(reVersion, '').trim();
let deed = null;
switch (which) {
case 'CC0':
deed = 'https://creativecommons.org/licenses/zero/1.0/';
break;
case 'CC BY':
deed = `https://creativecommons.org/licenses/by/${version}/`;
break;
case 'CC BY-SA':
deed = `https://creativecommons.org/licenses/by-sa/${version}/`;
break;
case 'CC BY-NC':
deed = `https://creativecommons.org/licenses/by-nc/${version}/`;
break;
case 'CC BY-NC-SA':
deed = `https://creativecommons.org/licenses/by-nc-sa/${version}/`;
break;
case 'CC BY-ND':
deed = `https://creativecommons.org/licenses/by-nd/${version}/`;
break;
case 'CC BY-NC-ND':
deed = `https://creativecommons.org/licenses/by-nc-nd/${version}/`;
break;
}
if (deed) {
license = `<a href='${deed}' onclick='window.open('${deed}')' target='_blank' rel='noopener'>${this.options.license}</a>`;
} else {
license = this.options.license;
}
return 'License: ' + license;
}
/**
*
* Helper class to clear menu items before rebuild
*
* @param {*} className Name of a class
*/
removeElementsByClass(className) {
// Need to prevent the menu from not showing sometimes
document.querySelectorAll('.vjs-sm-top-level').forEach(element => {
element.classList.remove('vjs-hidden');
});
const elements = document.getElementsByClassName(className);
while (elements.length > 0) {
elements[0].parentNode.removeChild(elements[0]);
}
}
}
// Define default values for the plugin's `state` object here.
License.defaultState = {};
// Include the version number.
License.VERSION = version;
// Register the plugin with video.js.
videojs__default["default"].registerPlugin('license', License);
return License;
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -82,15 +82,14 @@
liveui: true,
responsive: true,
fluid: true,
sources: [{ src: playerConfig.source, type: 'application/x-mpegURL' }],
plugins: {
license: playerConfig.license
}
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
};
if (playerConfig.chromecast) {
config.techOrder = ["chromecast", "html5"];
config.plugins.chromecast = {};
config.plugins.chromecast = {
receiverApplicationId: 'CC1AD845'
};
}
if (playerConfig.airplay) {
@ -130,6 +129,8 @@
},
],
});
player.license(playerConfig.license);
}
if (autoplay === true) {

View File

@ -1,67 +0,0 @@
var plugins = [];
if (statistics == true) {
plugins.push(ClapprNerdStats);
plugins.push(ClapprStats);
}
var config = {
source: playerConfig.source,
parentId: '#player',
baseUrl: 'clappr/',
persistConfig: false,
plugins: plugins,
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
mediacontrol: {
seekbar: playerConfig.color.seekbar,
buttons: color,
},
height: '100%',
width: '100%',
disableCanAutoPlay: true,
autoPlay: autoplay,
mimeType: 'application/vnd.apple.mpegurl',
actualLiveTime: false,
exitFullscreenOnEnd: false,
mute: mute,
playback: {
controls: false,
playInline: true,
recycleVideo: Clappr.Browser.isMobile,
hlsjsConfig: {
enableWorker: false,
capLevelToPlayerSize: true,
capLevelOnFPSDrop: true,
maxBufferHole: 1,
highBufferWatchdogPeriod: 1,
},
},
hlsPlayback: {
preload: false,
},
visibilityEnableIcon: false,
clapprStats: {
runEach: 1000,
onReport: (metrics) => {},
},
clapprNerdStats: {
shortcut: ['command+shift+s', 'ctrl+shift+s'],
iconPosition: 'top-right',
},
};
if (playerConfig.logo.image.length != 0) {
config.watermark = playerConfig.logo.image;
config.position = playerConfig.logo.position;
if (playerConfig.logo.link.length != 0) {
config.watermarkLink = playerConfig.logo.link;
}
}
var player = new window.Clappr.Player(config);
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster() {
posterPlugin.options.poster = playerConfig.poster + '?t=' + String(new Date().getTime());
posterPlugin.render();
});

View File

@ -438,11 +438,11 @@
</div>
<div class="col-xs-12 channel-list-inside">
{{#each channels}}
<a class="channel-list-link" href="/playersite_{{this.channelid}}.html" target="_self">
<a class="channel-list-link" href="playersite_{{this.channelid}}.html" target="_self">
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
<div class="col-xs-12 middle-xs channel-list-l2">
<span id="channel_list_badge_{{this.channelid}}_1" class="channel-list-badge">LIVE</span>
<img src="/memfs/{{this.channelid}}.jpg" class="channel-list-image">
<img src="memfs/{{this.channelid}}.jpg" class="channel-list-image">
<div class="row">
<div class="channel-list-description">
{{this.name}}
@ -498,11 +498,11 @@
</div>
<div class="col-xs-12 col-sm-12">
{{#each channels}}
<a class="channel-list-link" href="/playersite_{{this.channelid}}.html" target="_self">
<a class="channel-list-link" href="playersite_{{this.channelid}}.html" target="_self">
<div class="row channel-list-l1 {{#ifEquals @root.channel_id this.channelid}}channel-list-selected{{/ifEquals}}">
<div class="col-xs-12 middle-xs channel-list-l2">
<span id="channel_list_badge_{{this.channelid}}_2" class="channel-list-badge">LIVE</span>
<img src="/memfs/{{this.channelid}}.jpg" class="channel-list-image">
<img src="memfs/{{this.channelid}}.jpg" class="channel-list-image">
<div class="row">
<div class="channel-list-description">
{{this.name}}
@ -619,6 +619,7 @@
{{/if}}{{/if}}
<script>
{{#if share}}
var modal_share = document.getElementById("modal-share");
var btn_share = document.getElementById("btn-share");
var close_share = document.getElementById("close-share");
@ -628,6 +629,7 @@
close_share.onclick = function() {
modal_share.style.display = "none";
}
{{/if}}
{{#if imprint_html}}
var modal_imprint = document.getElementById("modal-imprint");
var btn_imprint = document.getElementById("btn-imprint");
@ -664,7 +666,9 @@
var close = document.getElementsByClassName("close")[0];
close.onclick = function() {
{{#if share}}
modal_share.style.display = "none";
{{/if}}
{{#if imprint_html}}
modal_imprint.style.display = "none";
{{/if}}
@ -676,20 +680,29 @@
{{/if}}{{/if}}
}
window.onclick = function(event) {
if (event.target == modal_share) {
modal_share.style.display = "none";
{{#if imprint_html}}
} else if (event.target == modal_imprint) {
modal_imprint.style.display = "none";
{{/if}}
{{#if terms_html}}
} else if (event.target == modal_terms) {
modal_terms.style.display = "none";
{{/if}}
{{#if channel_creator_name}}{{#if channel_creator_description_html}}
} else if (event.target == modal_creator) {
modal_creator.style.display = "none";
{{/if}}{{/if}}
switch(event.target) {
{{#if share}}
case modal_share:
modal_share.style.display = "none";
break;
{{/if}}
{{#if imprint_html}}
case modal_imprint:
modal_imprint.style.display = "none";
break;
{{/if}}
{{#if terms_html}}
case modal_terms:
modal_terms.style.display = "none";
break;
{{/if}}
{{#if channel_creator_name}}{{#if channel_creator_description_html}}
case modal_creator:
modal_creator.style.display = "none";
break;
{{/if}}{{/if}}
default:
break;
}
}
</script>
@ -698,10 +711,10 @@
var license = '{{channel_license}}';
var license_url = '';
var license_name = 'unknown';
if (license === 'CC0 4.0') {
if (license === 'CC0 1.0') {
license_name = 'CC0 1.0 Universal'
license_url = 'https://creativecommons.org/publicdomain/zero/1.0/';
license_image = 'https://creativecommons.org/publicdomain/zero/1.0/';
license_image = 'https://mirrors.creativecommons.org/presskit/buttons/88x31/png/pd.png';
} else if (license === 'CC BY 4.0') {
license_name = 'CC BY 4.0'
license_url = 'https://creativecommons.org/licenses/by/4.0/';
@ -725,7 +738,7 @@
} else if (license === 'CC BY-NC-ND 4.0') {
license_name = 'CC BY-NC-ND 4.0';
license_url = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
license_image = 'https://creativecommons.org/licenses/by-nc-nd/4.0/';
license_image = 'https://mirrors.creativecommons.org/presskit/buttons/88x31/png/by-nc-nd.png';
}
document.getElementById("license").setAttribute("href", license_url);
document.getElementById("license_image").src = license_image;

View File

@ -6,25 +6,32 @@ var config = {
liveui: true,
responsive: true,
fluid: true,
// Needed to append the url orgin in order for the source to properly pass to the cast device
// Needed to append the url origin in order for the source to properly pass to the cast device
sources: [{ src: window.location.origin + '/' + playerConfig.source, type: 'application/x-mpegURL' }],
plugins: {
license: playerConfig.license,
},
plugins: {},
};
if (chromecast) {
config.techOrder = ['chromecast', 'html5'];
config.plugins.chromecast = {};
}
if (airplay) {
config.plugins.airPlay = {};
// Provide a default reciever application ID
config.plugins.chromecast = {
receiverApplicationId: 'CC1AD845',
};
}
var player = videojs('player', config);
player.ready(function () {
if (chromecast) {
player.chromecast();
}
if (airplay) {
player.airPlay();
}
player.license(playerConfig.license);
if (playerConfig.logo.image.length != 0) {
var overlay = null;

View File

@ -4,7 +4,7 @@
<meta charset="utf-8" />
<link rel="icon" href="favicon.ico" />
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<meta name="theme-color" content="#000000" />
<meta name="theme-color" content="#282728" />
<meta name="description" content="Restreamer Video-Streaming" />
<link rel="apple-touch-icon" href="logo192.png" />
<link rel="manifest" href="manifest.json" />

View File

@ -20,6 +20,6 @@
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"theme_color": "#282728",
"background_color": "#282728"
}

View File

@ -2,45 +2,63 @@ import React from 'react';
import { I18nProvider } from '@lingui/react';
import { i18n } from '@lingui/core';
import * as plurals from 'make-plural/plurals';
import { messages as EN } from './locales/en/messages.js';
import { messages as DA } from './locales/da/messages.js';
import { messages as DE } from './locales/de/messages.js';
import { messages as EL } from './locales/el/messages.js';
import { messages as ES } from './locales/es/messages.js';
import { messages as FR } from './locales/fr/messages.js';
import { messages as IT } from './locales/it/messages.js';
import { messages as KO } from './locales/ko/messages.js';
import { messages as PL } from './locales/pl/messages.js';
import { messages as PT } from './locales/pt/messages.js';
import { messages as PT } from './locales/pt-br/messages.js';
import { messages as RU } from './locales/ru/messages.js';
import { messages as SL } from './locales/sl/messages.js';
import { messages as TR } from './locales/tr/messages.js';
import { messages as UK } from './locales/uk/messages.js';
import { messages as ZH } from './locales/zh-hans/messages.js';
import * as Storage from './utils/storage';
i18n.loadLocaleData('en', { plurals: plurals.en });
i18n.loadLocaleData('de', { plurals: plurals.de });
i18n.loadLocaleData('es', { plurals: plurals.es });
i18n.loadLocaleData('fr', { plurals: plurals.fr });
i18n.loadLocaleData('it', { plurals: plurals.it });
i18n.loadLocaleData('pl', { plurals: plurals.pl });
i18n.loadLocaleData('pt', { plurals: plurals.pt });
i18n.loadLocaleData('ru', { plurals: plurals.ru });
i18n.load({
en: EN,
da: DA,
de: DE,
el: EL,
es: ES,
fr: FR,
it: IT,
ko: KO,
pl: PL,
pt: PT,
'pt-br': PT,
ru: RU,
sl: SL,
tr: TR,
uk: UK,
'zh-hans': ZH,
});
const getLanguage = (defaultLanguage, supportedLanguages) => {
let lang = Storage.Get('language');
if (supportedLanguages.indexOf(lang) === -1) {
lang = getBrowserLanguage(defaultLanguage);
const aliases = {
pt: 'pt-br',
'zh-cn': 'zh-hans',
};
const getAlias = (lang) => {
if (lang in aliases) {
return aliases[lang];
}
return lang;
};
const getLanguage = (defaultLanguage, supportedLanguages) => {
let lang = getAlias(Storage.Get('language'));
if (supportedLanguages.indexOf(lang) === -1) {
lang = defaultLanguage;
lang = getAlias(getBrowserLanguage(defaultLanguage));
if (supportedLanguages.indexOf(lang) === -1) {
lang = defaultLanguage;
}
}
Storage.Set('language', lang);
@ -51,7 +69,7 @@ const getLanguage = (defaultLanguage, supportedLanguages) => {
const getBrowserLanguage = (defaultLanguage) => {
let lang = window.navigator.language;
const match = lang.match(/^[a-z]+/);
const match = lang.match(/^[a-z]+(-[a-z]+)?/i);
if (!match) {
return defaultLanguage;
}
@ -59,7 +77,7 @@ const getBrowserLanguage = (defaultLanguage) => {
return match[0].toLowerCase();
};
i18n.activate(getLanguage('en', ['en', 'de', 'es', 'fr', 'it', 'pl', 'pt', 'ru']));
i18n.activate(getLanguage('en', ['en', 'da', 'de', 'el', 'es', 'fr', 'it', 'ko', 'pl', 'pt-br', 'ru', 'sl', 'tr', 'uk', 'zh-hans']));
export default function Provider(props) {
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;

View File

@ -14,9 +14,15 @@ import ChannelList from './misc/ChannelList';
import Footer from './Footer';
import I18n from './I18n';
import Header from './Header';
import * as M from './utils/metadata';
import Restreamer from './utils/restreamer';
import Router from './Router';
import Views from './views';
import { UI as Version } from './version';
import Changelog from './misc/Changelog';
import SemverGt from 'semver/functions/gt';
import SemverValid from 'semver/functions/valid';
const useStyles = makeStyles((theme) => ({
MainHeader: {
@ -58,6 +64,12 @@ export default function RestreamerUI(props) {
channelid: '',
channels: [],
});
const [$metadata, setMetadata] = React.useState({});
const [$changelog, setChangelog] = React.useState({
open: false,
current: '',
previous: '',
});
const restreamer = React.useRef(null);
@ -131,6 +143,8 @@ export default function RestreamerUI(props) {
const valid = await restreamer.current.Validate();
await checkChangelog();
setState({
...$state,
initialized: true,
@ -146,8 +160,93 @@ export default function RestreamerUI(props) {
setReady(true);
};
const checkChangelog = async () => {
let showChangelog = true;
if (restreamer.current.IsConnected() === true) {
let metadata = await restreamer.current.GetMetadata(false);
const channels = await restreamer.current.ListChannels();
let current = Version.replace('restreamer-', '');
let previous = '';
if (SemverValid(current) === null) {
showChangelog = false;
}
if (metadata === null) {
if (channels.length === 1) {
const progress = await restreamer.current.GetIngestProgress(channels[0].channelid);
if (progress.valid === false) {
// assume fresh installation
metadata = M.initMetadata(metadata);
await restreamer.current.SetMetadata({
...metadata,
bundle: {
...metadata.bundle,
version: current,
},
});
return false;
}
}
}
metadata = M.initMetadata(metadata);
if ('version' in metadata.bundle) {
if (SemverValid(metadata.bundle.version) !== null) {
previous = metadata.bundle.version;
}
}
if (showChangelog === true) {
if (SemverValid(previous) === null) {
previous = '';
}
if (previous.length !== 0) {
if (!SemverGt(current, previous)) {
showChangelog = false;
}
}
}
setMetadata({
...$metadata,
...metadata,
});
setChangelog({
...$changelog,
open: showChangelog,
current: current,
previous: previous,
});
}
return showChangelog;
};
const handleCloseChangelog = async () => {
await restreamer.current.SetMetadata({
...$metadata,
bundle: {
...$metadata.bundle,
version: $changelog.current,
},
});
setChangelog({
...$changelog,
open: false,
});
};
const handleLogin = async (username, password) => {
const connected = await restreamer.current.Login(username, password);
await checkChangelog();
setState({
...$state,
connected: connected,
@ -196,16 +295,25 @@ export default function RestreamerUI(props) {
});
};
const handlePasswordReset = async (username, password) => {
const [, err] = await restreamer.current.ConfigSet({
const handlePasswordReset = async (username, loginUsername, password, loginPassword) => {
const data = {
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';
@ -249,7 +357,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(username, password);
await restreamer.current.Login(loginUsername, loginPassword);
window.location.reload();
} else {
@ -348,30 +456,42 @@ export default function RestreamerUI(props) {
return null;
};
let view = <Views.Initializing />;
if ($state.valid === false) {
view = <Views.Invalid address={restreamer.current.Address()} />;
} else if ($state.connected === false) {
view = (
<Views.Login
onLogin={handleLogin}
auths={restreamer.current.Auths()}
hasService={$state.service}
address={restreamer.current.Address()}
onAuth0={handleAuth0}
/>
);
} else if ($state.compatibility.compatible === false) {
if ($state.compatibility.core.compatible === false) {
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
} else if ($state.compatibility.ffmpeg.compatible === false) {
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
}
} else if ($state.password === true) {
view = <Views.Password onReset={handlePasswordReset} />;
let view = null;
if ($state.initialized === false) {
view = <Views.Initializing />;
} else {
view = <Router restreamer={restreamer.current} />;
resources = handleResources;
if ($state.valid === false) {
view = <Views.Invalid address={restreamer.current.Address()} />;
} else if ($state.connected === false) {
view = (
<Views.Login
onLogin={handleLogin}
auths={restreamer.current.Auths()}
hasService={$state.service}
address={restreamer.current.Address()}
onAuth0={handleAuth0}
/>
);
} else if ($state.compatibility.compatible === false) {
if ($state.compatibility.core.compatible === false) {
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
} else if ($state.compatibility.ffmpeg.compatible === false) {
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
}
} else if ($state.password === true) {
view = (
<Views.Password
onReset={handlePasswordReset}
username={restreamer.current.ConfigValue('api.auth.username')}
usernameOverride={restreamer.current.ConfigOverrides('api.auth.username')}
password={restreamer.current.ConfigValue('api.auth.password')}
passwordOverride={restreamer.current.ConfigOverrides('api.auth.password')}
/>
);
} else {
view = <Router restreamer={restreamer.current} />;
resources = handleResources;
}
}
const expand = $state.connected && $state.compatibility.compatible && !$state.password;
@ -428,6 +548,9 @@ export default function RestreamerUI(props) {
onState={handleStateChannel}
/>
)}
{expand && $changelog.open && (
<Changelog open={$changelog.open} onClose={handleCloseChangelog} current={$changelog.current} previous={$changelog.previous} />
)}
</NotifyProvider>
</I18n>
);

View File

@ -1,5 +1,5 @@
import React from 'react';
import { Route, Navigate, Routes, HashRouter } from 'react-router-dom';
import { Route, Navigate, Routes, HashRouter as DOMRouter } from 'react-router-dom';
import Views from './views';
@ -11,9 +11,9 @@ export default function Router(props) {
const channelid = props.restreamer.GetCurrentChannelID();
return (
<HashRouter>
<DOMRouter>
<Routes>
<Route path="/" element={<Views.ChannelSelect restreamer={props.restreamer} />} />
<Route path="/" element={<Views.ChannelSelect channelid={channelid} />} />
<Route path="/playersite" element={<Views.Playersite restreamer={props.restreamer} />} />
<Route path="/settings" element={<Views.Settings restreamer={props.restreamer} />} />
<Route path="/settings/:tab" element={<Views.Settings restreamer={props.restreamer} />} />
@ -26,7 +26,7 @@ export default function Router(props) {
<Route path="/:channelid/publication/:service/:index" element={<Views.EditService key={channelid} restreamer={props.restreamer} />} />
<Route path="*" render={() => <Navigate to="/" replace />} />
</Routes>
</HashRouter>
</DOMRouter>
);
}

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import '@fontsource/dosis';
@ -10,18 +10,20 @@ import theme from './theme';
import RestreamerUI from './RestreamerUI';
let address = window.location.protocol + '//' + window.location.host;
if (window.location.pathname.endsWith('/ui/')) {
address += window.location.pathname.replace(/ui\/$/, '');
}
const urlParams = new URLSearchParams(window.location.search.substring(1));
if (urlParams.has('address') === true) {
address = urlParams.get('address');
}
ReactDOM.render(
createRoot(document.getElementById('root')).render(
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<CssBaseline />
<RestreamerUI address={address} />
</ThemeProvider>
</StyledEngineProvider>,
document.getElementById('root')
</StyledEngineProvider>
);

File diff suppressed because one or more lines are too long

3451
src/locales/da/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3451
src/locales/el/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3451
src/locales/ko/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3451
src/locales/sl/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3451
src/locales/tr/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

3451
src/locales/uk/messages.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -47,8 +47,8 @@ export default function Component(props) {
return (
<Stack
direction="column"
justifyContent="center"
alignItems="center"
justifyContent={props.justifyContent}
alignItems={props.alignItems}
textAlign={props.textAlign}
spacing={1}
className={
@ -64,4 +64,6 @@ export default function Component(props) {
Component.defaultProps = {
color: 'light',
textAlign: 'left',
alignItems: 'center',
justifyContent: 'center',
};

197
src/misc/Changelog.js Normal file
View File

@ -0,0 +1,197 @@
import React from 'react';
import { Trans } from '@lingui/macro';
import makeStyles from '@mui/styles/makeStyles';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import ReactMarkdown from 'react-markdown';
import SemverGt from 'semver/functions/gt';
import SemverLte from 'semver/functions/lte';
import SemverEq from 'semver/functions/eq';
import SemverValid from 'semver/functions/valid';
import BoxText from './BoxText';
import Dialog from './modals/Dialog';
const useStyles = makeStyles((theme) => ({
h1: {
fontFamily: theme.typography.h1.fontFamily,
fontSize: theme.typography.h1.fontSize,
marginTop: '.5rem',
marginBottom: '-1rem',
},
h2: {
fontFamily: theme.typography.h2.fontFamily,
fontSize: theme.typography.h2.fontSize,
paddingTop: '1.5rem',
marginBottom: theme.typography.h2.marginBottom,
'&::after': {
content: '" "',
display: 'block',
height: 1,
backgroundColor: theme.palette.primary.contrastText,
},
},
h3: {
fontFamily: theme.typography.h3.fontFamily,
fontSize: theme.typography.h3.fontSize,
paddingTop: '.5rem',
marginBottom: theme.typography.h3.marginBottom,
},
h4: {
fontFamily: theme.typography.h4.fontFamily,
fontSize: theme.typography.h4.fontSize,
marginBottom: theme.typography.h4.marginBottom,
},
a: {
fontWeight: 'bold',
color: theme.palette.secondary.main,
},
}));
export default function Changelog(props) {
const [$data, setData] = React.useState('');
const classes = useStyles();
React.useEffect(() => {
(async () => {
await onMount();
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onMount = async () => {
let data = await loadData();
data = filter(data, props.current, props.previous);
setData(data);
};
const loadData = async () => {
let response = null;
try {
response = await fetch('CHANGELOG.md', {
method: 'GET',
});
} catch (err) {
return '';
}
if (response.ok === false) {
return '';
}
return await response.text();
};
const filter = (data, current, previous) => {
let lines = data.split('\n');
let filteredLines = [];
let copy = true;
for (let i = 0; i < lines.length; i++) {
if (lines[i].startsWith('## ')) {
let version = lines[i].replace('## ', '');
if (SemverValid(version) === null) {
if (copy === true) {
filteredLines.push(lines[i]);
}
continue;
}
if (current.length === 0) {
current = version;
}
if (previous.length === 0) {
previous = version;
}
if (SemverEq(current, previous)) {
if (SemverEq(version, current)) {
copy = true;
} else {
copy = false;
}
} else {
if (SemverLte(version, current) && SemverGt(version, previous)) {
copy = true;
} else {
copy = false;
}
}
}
if (copy === true) {
filteredLines.push(lines[i]);
}
}
return filteredLines.join('\n');
};
if ($data.length === 0 || $data.startsWith('<!DOCTYPE')) {
return null;
}
const renderers = {
h1: (props) => (
<h1 className={classes.h1} {...props}>
{props.children}
</h1>
),
h2: (props) => (
<h2 className={classes.h2} {...props}>
{props.children}
</h2>
),
h3: (props) => (
<h3 className={classes.h3} {...props}>
{props.children}
</h3>
),
h4: (props) => (
<h4 className={classes.h4} {...props}>
{props.children}
</h4>
),
a: (props) => (
<a className={classes.a} target="_blank" {...props}>
{props.children}
</a>
),
};
return (
<Dialog
open={props.open}
onClose={props.onClose}
title={<Trans>Update details (Changelog)</Trans>}
maxWidth={600}
buttonsRight={
<Button variant="outlined" color="primary" onClick={props.onClose}>
<Trans>Close</Trans>
</Button>
}
>
<Grid container spacing={2}>
<Grid item xs={12}>
<BoxText alignItems="left">
<ReactMarkdown components={renderers}>{$data}</ReactMarkdown>
</BoxText>
</Grid>
</Grid>
</Dialog>
);
}
Changelog.defaultProps = {
open: false,
current: '',
previous: '',
onClose: () => {},
};

View File

@ -10,6 +10,8 @@ import Button from '@mui/material/Button';
import ButtonBase from '@mui/material/ButtonBase';
import CloseIcon from '@mui/icons-material/Close';
import DoNotDisturbAltIcon from '@mui/icons-material/DoNotDisturbAlt';
import FullscreenIcon from '@mui/icons-material/Fullscreen';
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
import Grid from '@mui/material/Grid';
import IconButton from '@mui/material/IconButton';
import LensIcon from '@mui/icons-material/Lens';
@ -119,7 +121,7 @@ const ImageBackdrop = styled('span')(({ theme }) => ({
border: `2px solid ${theme.palette.primary.dark}`,
}));
function ChannelButton(props) {
function ChannelButton(props, largeChannelList) {
const classes = useStyles();
const theme = useTheme();
@ -154,7 +156,7 @@ function ChannelButton(props) {
}
return (
<Grid item xs={12} sm={6} md={4} lg={3}>
<Grid item xs={12} sm={6} md={4} lg={3} style={{ paddingBottom: largeChannelList ? '10px' : 'auto' }}>
<ImageButton focusRipple disabled={props.disabled} onClick={props.onClick} style={{ width: props.width }}>
<Stack direction="column" spacing={0.5}>
<Image
@ -192,6 +194,21 @@ ChannelButton.defaultProps = {
onClick: () => {},
};
const calculateColumnsPerRow = (breakpointSmall, breakpointMedium, breakpointLarge) => {
if (breakpointLarge) {
return 4;
} else if (breakpointMedium) {
return 3;
} else if (breakpointSmall) {
return 2;
}
return 1;
};
const calculateRowsToFit = (windowHeight, thumbnailHeight, otherUIHeight) => {
return Math.floor((windowHeight - otherUIHeight) / thumbnailHeight);
};
export default function ChannelList(props) {
const classes = useStyles();
const theme = useTheme();
@ -205,8 +222,12 @@ export default function ChannelList(props) {
open: false,
name: '',
});
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
const [windowHeight, setWindowHeight] = React.useState(window.innerHeight);
const { channels: allChannels, channelid, onClick, onState } = props;
const { channels: allChannels, channelid, onClick, onClose, onState } = props;
const [$largeChannelList, setLargeChannelList] = React.useState(false);
React.useEffect(() => {
(async () => {
@ -246,19 +267,40 @@ export default function ChannelList(props) {
disabled={channelid === channel.channelid}
onClick={() => {
onClick(channel.channelid);
if ($largeChannelList) {
onClose();
}
}}
largeChannelList={$largeChannelList}
/>
);
});
setChannels(channels);
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [$pos, allChannels, $nChannels, channelid, onClick, onState]);
const onMount = async () => {
setPos(0);
};
React.useEffect(() => {
const handleResize = () => {
setWindowWidth(window.innerWidth);
setWindowHeight(window.innerHeight);
};
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
React.useEffect(() => {
const columns = calculateColumnsPerRow(breakpointSmall, breakpointMedium, breakpointLarge);
const rows = $largeChannelList ? calculateRowsToFit(windowHeight, 200, 60) : 1;
setNChannels(rows * columns);
}, [breakpointSmall, breakpointMedium, breakpointLarge, windowHeight, windowWidth, $largeChannelList]);
const handleAddChannelDialog = () => {
setAddChannel({
...$addChannel,
@ -282,6 +324,11 @@ export default function ChannelList(props) {
return null;
}
const handleLargeChannelList = () => {
setLargeChannelList(!$largeChannelList);
setPos(0);
};
return (
<React.Fragment>
<SwipeableDrawer
@ -289,7 +336,13 @@ export default function ChannelList(props) {
open={props.open}
onOpen={() => {}}
onClose={props.onClose}
sx={{ marginButtom: 60 }}
sx={{
marginButtom: 60,
'& .MuiDrawer-paper': {
top: $largeChannelList ? '0px!important' : 'auto!important',
height: $largeChannelList ? '100vh' : 'auto',
},
}}
BackdropProps={{ invisible: true }}
classes={{
paper: classes.drawerPaper,
@ -307,6 +360,9 @@ export default function ChannelList(props) {
<IconButton color="inherit" size="large" onClick={handleAddChannelDialog}>
<AddIcon />
</IconButton>
<IconButton color="inherit" size="large" onClick={handleLargeChannelList}>
{$largeChannelList ? <FullscreenExitIcon /> : <FullscreenIcon />}
</IconButton>
<IconButton color="inherit" size="large" onClick={props.onClose}>
<CloseIcon />
</IconButton>
@ -327,7 +383,7 @@ export default function ChannelList(props) {
</IconButton>
</Stack>
<Stack direction="row" spacing={0} className={classes.channelItems}>
<Grid container spacing={0} justifyContent="center">
<Grid container spacing={0} justifyContent={$largeChannelList ? 'flex-start' : 'center'}>
{$channels}
</Grid>
</Stack>

View File

@ -16,9 +16,19 @@ export default function EncodingSelect(props) {
const { i18n } = useLingui();
const profile = props.profile;
let availableEncoders = [];
let availableDecoders = [];
if (props.type === 'video') {
availableEncoders = props.skills.encoders.video;
availableDecoders = props.skills.decoders.video;
} else if (props.type === 'audio') {
availableEncoders = props.skills.encoders.audio;
}
const handleDecoderChange = (event) => {
const decoder = profile.decoder;
const stream = props.streams[profile.stream];
decoder.coder = event.target.value;
// If the coder changes, use the coder's default settings
@ -30,7 +40,7 @@ export default function EncodingSelect(props) {
}
if (c !== null) {
const defaults = c.defaults();
const defaults = c.defaults(stream, props.skills);
decoder.settings = defaults.settings;
decoder.mapping = defaults.mapping;
}
@ -49,6 +59,7 @@ export default function EncodingSelect(props) {
const handleEncoderChange = (event) => {
const encoder = profile.encoder;
const stream = props.streams[profile.stream];
encoder.coder = event.target.value;
// If the coder changes, use the coder's default settings
@ -60,7 +71,7 @@ export default function EncodingSelect(props) {
}
if (c !== null) {
const defaults = c.defaults({});
const defaults = c.defaults(stream, props.skills);
encoder.settings = defaults.settings;
encoder.mapping = defaults.mapping;
}
@ -116,10 +127,10 @@ export default function EncodingSelect(props) {
let encoderSettingsHelp = null;
let coder = encoderRegistry.Get(profile.encoder.coder);
if (coder !== null && props.availableEncoders.includes(coder.coder)) {
if (coder !== null && availableEncoders.includes(coder.coder)) {
const Settings = coder.component;
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} onChange={handleEncoderSettingsChange} />;
encoderSettings = <Settings stream={stream} settings={profile.encoder.settings} skills={props.skills} onChange={handleEncoderSettingsChange} />;
if (props.type === 'video' && !['copy', 'none', 'rawvideo'].includes(coder.coder)) {
encoderSettingsHelp = handleEncoderHelp(coder.coder);
@ -130,7 +141,7 @@ export default function EncodingSelect(props) {
for (let c of encoderRegistry.List()) {
// Does ffmpeg support the coder?
if (!props.availableEncoders.includes(c.coder)) {
if (!availableEncoders.includes(c.coder)) {
continue;
}
@ -144,14 +155,14 @@ export default function EncodingSelect(props) {
encoderList.push(
<MenuItem value={c.coder} key={c.coder}>
{c.name}
</MenuItem>
</MenuItem>,
);
}
} else {
encoderList.push(
<MenuItem value={c.coder} key={c.coder}>
{c.name}
</MenuItem>
</MenuItem>,
);
}
}
@ -173,22 +184,24 @@ export default function EncodingSelect(props) {
if (coder.coder !== 'copy' && coder.coder !== 'none') {
let c = decoderRegistry.Get(profile.decoder.coder);
if (c !== null && props.availableDecoders.includes(c.coder)) {
if (c !== null && availableDecoders.includes(c.coder)) {
const Settings = c.component;
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} onChange={handleDecoderSettingsChange} />;
decoderSettings = <Settings stream={stream} settings={profile.decoder.settings} skills={props.skills} onChange={handleDecoderSettingsChange} />;
}
// List all decoders for the codec of the stream
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, props.availableDecoders, 'any')) {
for (let c of decoderRegistry.GetCodersForCodec(stream.codec, availableDecoders, 'any')) {
decoderList.push(
<MenuItem value={c.coder} key={c.coder}>
{c.name}
</MenuItem>
</MenuItem>,
);
}
}
// TODO: in case there's no decoder for a codec it should be mentioned.
return (
<Grid container spacing={2}>
<Grid item xs={12}>
@ -237,7 +250,6 @@ EncodingSelect.defaultProps = {
streams: [],
profile: {},
codecs: [],
availableEncoders: [],
availableDecoders: [],
skills: {},
onChange: function (encoder, decoder, automatic) {},
};

28
src/misc/Filesize.js Normal file
View File

@ -0,0 +1,28 @@
import React from 'react';
// Adapted from https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
export default function Filesize(props) {
let bytes = props.bytes;
const thresh = props.si ? 1000 : 1024;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = props.si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
let u = -1;
const r = 10 ** props.digits;
do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return <React.Fragment>{bytes.toFixed(props.digits) + ' ' + units[u]}</React.Fragment>;
}
Filesize.defaultProps = {
bytes: 0,
si: false,
digits: 1,
};

View File

@ -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)} />,
);
}
}

View File

@ -42,14 +42,21 @@ export default function LanguageSelect(props) {
return (
<Select className={classes.root} variant="standard" displayEmpty value={i18n.locale} onChange={handleChange}>
<MenuItem value="en">English </MenuItem>
<MenuItem value="de">Deutsch </MenuItem>
<MenuItem value="es">Español </MenuItem>
<MenuItem value="fr">Français </MenuItem>
<MenuItem value="it">Italiano </MenuItem>
<MenuItem value="en">English</MenuItem>
<MenuItem value="da">Dansk</MenuItem>
<MenuItem value="de">Deutsch</MenuItem>
<MenuItem value="el">Ελληνικά</MenuItem>
<MenuItem value="es">Español</MenuItem>
<MenuItem value="fr">Français</MenuItem>
<MenuItem value="it">Italiano</MenuItem>
<MenuItem value="ko">한국어</MenuItem>
<MenuItem value="pl">Polski</MenuItem>
<MenuItem value="pt">Português </MenuItem>
<MenuItem value="ru">Русский </MenuItem>
<MenuItem value="pt-br">Português</MenuItem>
<MenuItem value="ru">Русский</MenuItem>
<MenuItem value="sl">Slovenščina</MenuItem>
<MenuItem value="tr">Türkçe</MenuItem>
<MenuItem value="uk">Українська мова</MenuItem>
<MenuItem value="zh-hans">中文(简体)</MenuItem>
</Select>
);
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

@ -15,16 +15,9 @@ const MenuProps = {
export default function Component(props) {
return (
<FormControl variant="outlined" fullWidth>
<FormControl variant={props.variant} disabled={props.disabled} fullWidth>
<InputLabel>{props.label}</InputLabel>
<Select
multiple
value={props.value}
onChange={props.onChange}
input={<OutlinedInput />}
renderValue={(selected) => selected.join(', ')}
MenuProps={MenuProps}
>
<Select multiple value={props.value} onChange={props.onChange} input={<OutlinedInput />} renderValue={props.renderValue} MenuProps={MenuProps}>
{props.children}
</Select>
</FormControl>
@ -32,7 +25,10 @@ export default function Component(props) {
}
Component.defaultProps = {
variant: 'outlined',
label: '',
value: [],
disabled: false,
renderValue: (selected) => selected.join(', '),
onChange: function (event) {},
};

View File

@ -1,101 +0,0 @@
import React from 'react';
import { Plugins } from '@clappr/plugins';
import Clappr from '@clappr/core';
import Grid from '@mui/material/Grid';
import HLS from '@clappr/hlsjs-playback';
//import ClapprStats from '@clappr/stats-plugin';
/*
import Clappr from 'clappr';
import ClapprStats from 'clappr-stats';
*/
//import ClapprNerdStats from 'clappr-nerd-stats';
Clappr.Loader.registerPlayback(HLS);
for (let plugin of Object.values(Plugins)) {
Clappr.Loader.registerPlugin(plugin);
}
class Player extends React.Component {
constructor(props) {
super(props);
this.player = new Clappr.Player({});
this.playerRef = React.createRef();
}
componentDidMount() {
this.player.attachTo(this.playerRef.current);
this.setConfig(this.props.config);
this.setSource(this.props.source);
}
componentWillUnmount() {
this.player.destroy();
}
shouldComponentUpdate(nextProps, _) {
if (nextProps.source !== this.props.source) {
this.setSource(nextProps.source);
}
return false;
}
setSource(source) {
this.player.load(source);
}
setConfig(config) {
delete config.source;
delete config.sources;
delete config.parent;
delete config.parentid;
config = {
plugins: [],
...config,
};
const plugins = config.plugins;
config.plugins = [];
for (let p of plugins) {
switch (p) {
/*
case 'ClapprStats':
config.plugins.push(ClapprStats);
break;
*/
/*
case 'ClapprNerdStats':
config.plugins.push(ClapprNerdStats);
break;
*/
default:
break;
}
}
this.player.configure(config);
}
render() {
return (
<Grid
container
direction="column"
justifyContent="center"
alignItems="center"
spacing={1}
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
ref={this.playerRef}
></Grid>
);
}
}
export default Player;

View File

@ -1,10 +1,9 @@
import React from 'react';
import Clappr from './clappr';
import VideoJS from './videojs';
export default function Player(props) {
const type = props.type ? props.type : 'clappr';
const type = props.type ? props.type : 'videojs-internal';
if (type === 'videojs-internal' || type === 'videojs-public') {
const config = {
@ -15,6 +14,9 @@ export default function Player(props) {
liveui: true,
responsive: true,
fluid: true,
plugins: {
reloadSourceOnError: {},
},
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
};
@ -43,17 +45,19 @@ export default function Player(props) {
overlay = imgTag.outerHTML;
}
player.overlay({
align: props.logo.position,
overlays: [
{
showBackground: false,
content: overlay,
start: 'playing',
end: 'pause',
},
],
});
if (player.overlay) {
player.overlay({
align: props.logo.position,
overlays: [
{
showBackground: false,
content: overlay,
start: 'playing',
end: 'pause',
},
],
});
}
}
if (props.autoplay === true) {
@ -76,57 +80,6 @@ export default function Player(props) {
}}
/>
);
} else {
const config = {
poster: props.poster,
autoPlay: props.autoplay,
mute: props.mute,
disableCanAutoPlay: true,
playback: {
playInline: true,
hlsjsConfig: {
enableWorker: false,
},
},
chromeless: !props.controls,
height: '100%',
width: '100%',
mediacontrol: {
seekbar: props.colors.seekbar,
buttons: props.colors.buttons,
},
};
if (props.logo.image.length !== 0) {
config.watermark = props.logo.image + '?' + Math.random();
config.position = props.logo.position;
if (props.logo.link.length !== 0) {
config.watermarkLink = props.logo.link;
}
}
if (props.ga.account.length !== 0) {
config.gaAccount = props.ga.account;
if (props.ga.name.length !== 0) {
config.gaTrackerName = props.ga.name;
}
}
if (props.statistics === true) {
config.plugins.push('ClapprStats', 'clapprNerdStats');
config.clapprStats = {
runEach: 1000,
onReport: (metrics) => {},
};
config.clapprNerdStats = {
shortcut: ['command+shift+s', 'ctrl+shift+s'],
iconPosition: 'top-right',
};
}
return <Clappr source={props.source} config={config} />;
}
}

View File

@ -89,7 +89,7 @@
}
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
top: -0.4em;
top: 0em;
right: -0.5em;
}

View File

@ -1,98 +1 @@
.vjs-internal {
--video-js--primary: #eaea05;
}
.vjs-internal .vjs-big-play-button {
width: 70px;
height: 70px;
background: 0 0;
line-height: 180px;
font-size: 180px;
border: none;
top: 50%;
left: 50%;
margin-top: -90px;
color: rgba(255, 255, 255, 0.65);
}
.vjs-internal.vjs-big-play-button:focus,
.vjs-internal:hover .vjs-big-play-button {
background-color: transparent;
color: rgba(255, 255, 255, 1);
}
.vjs-internal .vjs-control-bar {
top: calc(50% - 45px);
height: 100px;
width: 50px;
background-color: #4d4d4d;
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.vjs-internal .vjs-button > .vjs-icon-placeholder:before {
line-height: 50px;
}
.vjs-internal .vjs-play-progress:before {
display: none;
}
.vjs-internal .vjs-progress-control {
display: none;
}
.vjs-internal .vjs-paused,
.vjs-internal .vjs-playing {
width: 50px;
height: 50px;
border-radius: 4px;
background-color: #4d4d4d;
}
.vjs-internal .vjs-volume-panel {
width: 50px;
height: 50px;
border-radius: 4px;
padding-left: 4px;
background-color: #4d4d4d;
margin: 50px 0 0 -50px;
}
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
padding-top: 1em;
transition: visibility 1s, opacity 1s, height 1s 1s, width 1s, right 1s 1s,
top 1s 1s;
}
.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,
.vjs-internal .vjs-volume-panel .vjs-volume-control:active,
.vjs-internal
.vjs-volume-panel.vjs-hover
.vjs-mute-control
~ .vjs-volume-control,
.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,
.vjs-internal .vjs-volume-panel:active .vjs-volume-control,
.vjs-internal .vjs-volume-panel:focus .vjs-volume-control {
visibility: visible;
opacity: 1;
position: relative;
transition: visibility 0.1s, opacity 0.1s, height 0.1s, width 0.1s, right 0s,
top 0s;
}
.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before {
top: -0.4em;
right: -0.5em;
}
.vjs-internal .vjs-remaining-time {
display: none;
}
.vjs-internal .vjs-live-display {
display: none;
}
.vjs-internal .vjs-picture-in-picture-control {
display: none;
}
.vjs-internal .vjs-fullscreen-control {
display: none;
}
.vjs-internal .vjs-seek-to-live-control {
display: none;
}
.vjs-internal .vjs-subs-caps-button {
display: none;
}
.vjs-internal .vjs-custom-control-spacer {
display: block;
width: 100%;
}
.vjs-internal{--video-js--primary:#eaea05}.vjs-internal .vjs-big-play-button{width:70px;height:70px;background:0 0;line-height:180px;font-size:180px;border:none;top:50%;left:50%;margin-top:-90px;color:rgba(255,255,255,.65)}.vjs-internal.vjs-big-play-button:focus,.vjs-internal:hover .vjs-big-play-button{background-color:transparent;color:rgba(255,255,255,1)}.vjs-internal .vjs-control-bar{top:calc(50% - 45px);height:100px;width:50px;background-color:#4d4d4d;border-top-right-radius:4px;border-bottom-right-radius:4px}.vjs-internal .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-internal .vjs-play-progress:before{display:none}.vjs-internal .vjs-progress-control{display:none}.vjs-internal .vjs-paused,.vjs-internal .vjs-playing{width:50px;height:50px;border-radius:4px;background-color:#4d4d4d}.vjs-internal .vjs-volume-panel{width:50px;height:50px;border-radius:4px;padding-left:4px;background-color:#4d4d4d;margin:50px 0 0 -50px}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:1em;transition:visibility 1s,opacity 1s,height 1s 1s,width 1s,right 1s 1s,top 1s 1s}.vjs-internal .vjs-volume-panel .vjs-volume-control.vjs-slider-active,.vjs-internal .vjs-volume-panel .vjs-volume-control:active,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-mute-control~.vjs-volume-control,.vjs-internal .vjs-volume-panel.vjs-hover .vjs-volume-control,.vjs-internal .vjs-volume-panel:active .vjs-volume-control,.vjs-internal .vjs-volume-panel:focus .vjs-volume-control{visibility:visible;opacity:1;position:relative;transition:visibility .1s,opacity .1s,height .1s,width .1s,right 0s,top 0s}.vjs-internal .vjs-slider-horizontal .vjs-volume-level:before{top:0;right:-.5em}.vjs-internal .vjs-remaining-time{display:none}.vjs-internal .vjs-live-display{display:none}.vjs-internal .vjs-picture-in-picture-control{display:none}.vjs-internal .vjs-fullscreen-control{display:none}.vjs-internal .vjs-seek-to-live-control{display:none}.vjs-internal .vjs-subs-caps-button{display:none}.vjs-internal .vjs-custom-control-spacer{display:block;width:100%}

View File

@ -1,117 +1,117 @@
.vjs-public {
--video-js--primary: #eaea05;
--video-js--primary: #EAEA05;
}
/* play btn */
.vjs-public .vjs-big-play-button {
width: 70px;
height: 70px;
background: none;
line-height: 180px;
font-size: 180px;
border: none;
top: 50%;
left: 50%;
margin-top: -90px;
margin-left: -90px;
color: rgba(255, 255, 255, 0.65);
}
width: 70px;
height: 70px;
background: none;
line-height: 180px;
font-size: 180px;
border: none;
top: 50%;
left: 50%;
margin-top: -90px;
margin-left: -90px;
color: rgba(255,255,255,.65);
}
.vjs-public:hover .vjs-big-play-button,
.vjs-public.vjs-big-play-button:focus {
background-color: transparent;
color: rgba(255, 255, 255, 1);
}
.vjs-public:hover .vjs-big-play-button,
.vjs-public.vjs-big-play-button:focus {
background-color: transparent;
color: rgba(255,255,255,1);
}
/* controlbar */
.vjs-public .vjs-control-bar {
height: 70px;
padding-top: 20px;
background: none;
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
height: 70px;
padding-top: 20px;
background: none;
background-image: linear-gradient(0deg, rgba(0,0,0,.85), transparent)
}
.vjs-public .vjs-time-tooltip {
z-index: 0;
}
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
line-height: 50px;
.vjs-public .vjs-button>.vjs-icon-placeholder:before {
line-height: 50px
}
/* progressbar */
.vjs-public .vjs-play-progress:before {
display: none;
display: none
}
.vjs-public .vjs-progress-control {
position: absolute;
top: 0;
right: 0;
left: 15px;
width: calc(100% - 30px);
height: 20px;
position: absolute;
top: 0;
right: 0;
left: 15px;
width: calc(100% - 30px);
height: 20px
}
.vjs-public .vjs-progress-control .vjs-progress-holder {
position: absolute;
top: 20px;
right: 0;
left: 0;
width: 100%;
margin: 0;
position: absolute;
top: 20px;
right: 0;
left: 0;
width: 100%;
margin: 0
}
.vjs-public .vjs-play-progress {
background-color: var(--video-js--primary);
background-color: var(--video-js--primary);
}
.vjs-public .vjs-slider {
background: rgba(255, 255, 255, 0.25);
background: rgba(255,255,255,.25);
}
.vjs-public .vjs-load-progress {
background: rgba(255, 255, 255, 0.25);
background: rgba(255,255,255,.25);
}
.vjs-public .vjs-load-progress div {
background: rgba(255, 255, 255, 0.25);
background: rgba(255,255,255,.25);
}
.vjs-public .vjs-remaining-time {
order: 0;
line-height: 50px;
flex: 3;
text-align: left;
order: 0;
line-height: 50px;
flex: 3;
text-align: left;
}
.vjs-public .vjs-live-control {
line-height: 50px;
line-height: 50px;
}
/* volume-panel */
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
padding-top: 1em;
padding-top: .95em;
}
.vjs-public .vjs-control .vjs-volume-panel {
width: 4.5em;
width: 4.5em;
}
/* live display */
.vjs-public .vjs-live-display {
margin-left: 1.8em;
margin-left: 1.8em;
}
/* disable caps */
.vjs-internal .vjs-subs-caps-button {
display: none;
.vjs-public .vjs-subs-caps-button {
display: none!important;
}
/* spacer */
@ -123,47 +123,52 @@
/* overlay */
.vjs-public .vjs-overlay > a > img {
width: 100%;
.vjs-public .vjs-overlay-no-background {
max-width: 25%!important;
}
.vjs-public .vjs-overlay-no-background {
max-width: 28% !important;
max-height: 28% !important;
.vjs-public .vjs-overlay-no-background > img, .vjs-public .vjs-overlay-no-background > a > img {
max-width: 100%!important;
height: auto!important;
}
.vjs-public .vjs-overlay-top-left {
top: 20px !important;
left: 30px !important;
top: 15px!important;
left: 20px!important;
text-align: right;
}
.vjs-public .vjs-overlay-top-right {
top: 20px !important;
right: 30px !important;
top: 15px!important;
right: 20px!important;
text-align: right;
}
.vjs-public .vjs-overlay-bottom-left {
bottom: 20px !important;
left: 30px !important;
bottom: 15px!important;
left: 20px!important;
text-align: left;
}
.vjs-public .vjs-overlay-bottom-right {
bottom: 20px !important;
right: 30px !important;
bottom: 15px!important;
right: 20px!important;
text-align: left;
}
/* context menu */
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
background: rgba(0, 0, 0, 0.8);
background: rgba(0,0,0,.8);
}
.vjs-public .vjs-license-top-level-header {
background: unset !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
min-width: 100px;
background: unset!important;
border-bottom: 1px solid rgba(255,255,255,.25);
}
.vjs-public .vjs-lock-open {
z-index: 1000;
z-index: 1000;
}

View File

@ -1,123 +1 @@
.vjs-public {
--video-js--primary: #eaea05;
}
.vjs-public .vjs-big-play-button {
width: 70px;
height: 70px;
background: 0 0;
line-height: 180px;
font-size: 180px;
border: none;
top: 50%;
left: 50%;
margin-top: -90px;
margin-left: -90px;
color: rgba(255, 255, 255, 0.65);
}
.vjs-public.vjs-big-play-button:focus,
.vjs-public:hover .vjs-big-play-button {
background-color: transparent;
color: rgba(255, 255, 255, 1);
}
.vjs-public .vjs-control-bar {
height: 70px;
padding-top: 20px;
background: 0 0;
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
}
.vjs-public .vjs-time-tooltip {
z-index: 0;
}
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
line-height: 50px;
}
.vjs-public .vjs-play-progress:before {
display: none;
}
.vjs-public .vjs-progress-control {
position: absolute;
top: 0;
right: 0;
left: 15px;
width: calc(100% - 30px);
height: 20px;
}
.vjs-public .vjs-progress-control .vjs-progress-holder {
position: absolute;
top: 20px;
right: 0;
left: 0;
width: 100%;
margin: 0;
}
.vjs-public .vjs-play-progress {
background-color: var(--video-js--primary);
}
.vjs-public .vjs-slider {
background: rgba(255, 255, 255, 0.25);
}
.vjs-public .vjs-load-progress {
background: rgba(255, 255, 255, 0.25);
}
.vjs-public .vjs-load-progress div {
background: rgba(255, 255, 255, 0.25);
}
.vjs-public .vjs-remaining-time {
order: 0;
line-height: 50px;
flex: 3;
text-align: left;
}
.vjs-public .vjs-live-control {
line-height: 50px;
}
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
padding-top: 1em;
}
.vjs-public .vjs-control .vjs-volume-panel {
width: 4.5em;
}
.vjs-public .vjs-live-display {
margin-left: 1.8em;
}
.vjs-internal .vjs-subs-caps-button {
display: none;
}
.vjs-public .vjs-custom-control-spacer {
display: block;
width: 100%;
}
.vjs-public .vjs-overlay > a > img {
width: 100%;
}
.vjs-public .vjs-overlay-no-background {
max-width: 28% !important;
max-height: 28% !important;
}
.vjs-public .vjs-overlay-top-left {
top: 20px !important;
left: 30px !important;
}
.vjs-public .vjs-overlay-top-right {
top: 20px !important;
right: 30px !important;
}
.vjs-public .vjs-overlay-bottom-left {
bottom: 20px !important;
left: 30px !important;
}
.vjs-public .vjs-overlay-bottom-right {
bottom: 20px !important;
right: 30px !important;
}
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
background: rgba(0, 0, 0, 0.8);
}
.vjs-public .vjs-license-top-level-header {
background: unset !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
min-width: 100px;
}
.vjs-public .vjs-lock-open {
z-index: 1000;
}
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background:none;line-height:180px;font-size:180px;border:0;top:50%;left:50%;margin-top:-90px;margin-left:-90px;color:rgba(255,255,255,.65)}.vjs-public:hover .vjs-big-play-button,.vjs-public.vjs-big-play-button:focus{background-color:transparent;color:rgba(255,255,255,1)}.vjs-public .vjs-control-bar{height:70px;padding-top:20px;background:none;background-image:linear-gradient(0,rgba(0,0,0,.85),transparent)}.vjs-public .vjs-time-tooltip{z-index:0}.vjs-public .vjs-button>.vjs-icon-placeholder:before{line-height:50px}.vjs-public .vjs-play-progress:before{display:none}.vjs-public .vjs-progress-control{position:absolute;top:0;right:0;left:15px;width:calc(100% - 30px);height:20px}.vjs-public .vjs-progress-control .vjs-progress-holder{position:absolute;top:20px;right:0;left:0;width:100%;margin:0}.vjs-public .vjs-play-progress{background-color:var(--video-js--primary)}.vjs-public .vjs-slider{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress{background:rgba(255,255,255,.25)}.vjs-public .vjs-load-progress div{background:rgba(255,255,255,.25)}.vjs-public .vjs-remaining-time{order:0;line-height:50px;flex:3;text-align:left}.vjs-public .vjs-live-control{line-height:50px}.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal{padding-top:.95em}.vjs-public .vjs-control .vjs-volume-panel{width:4.5em}.vjs-public .vjs-live-display{margin-left:1.8em}.vjs-public .vjs-subs-caps-button{display:none!important}.vjs-public .vjs-custom-control-spacer{display:block;width:100%}.vjs-public .vjs-overlay-no-background{max-width:25%!important}.vjs-public .vjs-overlay-no-background>img,.vjs-public .vjs-overlay-no-background>a>img{max-width:100%!important;height:auto!important}.vjs-public .vjs-overlay-top-left{top:15px!important;left:20px!important;text-align:right}.vjs-public .vjs-overlay-top-right{top:15px!important;right:20px!important;text-align:right}.vjs-public .vjs-overlay-bottom-left{bottom:15px!important;left:20px!important;text-align:left}.vjs-public .vjs-overlay-bottom-right{bottom:15px!important;right:20px!important;text-align:left}.vjs-public .vjs-license .vjs-menu .vjs-menu-content{background:rgba(0,0,0,.8)}.vjs-public .vjs-license-top-level-header{background:unset!important;border-bottom:1px solid rgba(255,255,255,.25)}.vjs-public .vjs-lock-open{z-index:1000}

View File

@ -58,7 +58,7 @@ export default function VideoJS(props) {
direction="column"
justifyContent="center"
alignItems="center"
spacing={1}
spacing={2}
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
>
<div data-vjs-player>

View File

@ -28,6 +28,8 @@ function init(props) {
drop: 0,
dup: 0,
frames: 0,
cpu: 0,
memory: 0,
...props,
};
@ -57,7 +59,33 @@ export default function Progress(props) {
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={Math.round(progress.fps)} />
<Number value={progress.cpu} digits={2} minDigits={2} />%
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>CPU</Trans>
</Typography>
</Grid>
<Grid item xs={12}>
<Divider />
</Grid>
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={progress.memory / 1024 / 1024} digits={0} minDigits={0} /> MB
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Memory</Trans>
</Typography>
</Grid>
<Grid item xs={12}>
<Divider />
</Grid>
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={progress.fps} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
@ -70,7 +98,7 @@ export default function Progress(props) {
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={progress.bitrate} minDigits={2} />
<Number value={progress.bitrate} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
@ -83,7 +111,7 @@ export default function Progress(props) {
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={progress.q} digits={2} />
<Number value={progress.q} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
@ -96,7 +124,7 @@ export default function Progress(props) {
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={progress.speed} minDigits={2} />
<Number value={progress.speed} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
@ -108,7 +136,9 @@ export default function Progress(props) {
</Grid>
<Grid item xs={12}>
<Typography variant="h4">
<strong>{!isNaN(Math.round((props.drop * 100) / props.frames)) || 0}%</strong>
<strong>
<Number value={!isNaN((props.drop * 100) / props.frames) || 0} digits={2} minDigits={2} />%
</strong>
</Typography>
<Typography variant="body2" gutterBottom>
<Trans>Frame drops</Trans>
@ -120,7 +150,7 @@ export default function Progress(props) {
<Grid item xs={12}>
<Typography variant="h4">
<strong>
<Number value={Math.round(progress.dup)} />
<Number value={progress.dup} digits={2} minDigits={2} />
</strong>
</Typography>
<Typography variant="body2" gutterBottom>

View File

@ -41,7 +41,11 @@ export default function Component(props) {
isCustom: v === props.customKey ? true : false,
});
props.onChange(event);
props.onChange({
target: {
value: v === props.customKey ? value.custom : value.value,
},
});
};
const handleCustomChange = (event) => {
@ -59,7 +63,7 @@ export default function Component(props) {
options.push(
<MenuItem key={o.value} value={o.value} disabled={o.disabled === true}>
{o.label}
</MenuItem>
</MenuItem>,
);
}
@ -70,7 +74,7 @@ export default function Component(props) {
{$value.isCustom === true ? (
<React.Fragment>
<Grid item xs={6}>
<FormControl variant={props.variant} fullWidth MenuProps={{ disableScrollLock: true }}>
<FormControl variant={props.variant} fullWidth>
<InputLabel>{props.label}</InputLabel>
<Select
value={$value.isCustom === false ? $value.value : props.customKey}

97
src/misc/UploadButton.js Normal file
View File

@ -0,0 +1,97 @@
import React from 'react';
import FormInlineButton from './FormInlineButton';
export default function UploadButton(props) {
const { acceptTypes, label, onError, onStart, onUpload, ...other } = props;
const accept = props.acceptTypes.map((t) => t.mimetype);
const handleUpload = (event) => {
const handler = (event) => {
const files = event.target.files;
if (files.length === 0) {
// no files selected
props.onError({
type: 'nofiles',
});
return;
}
const file = files[0];
let type = null;
for (let t of props.acceptTypes) {
const accept = t.mimetype.split('/');
const actual = file.type.split('/');
if (accept[0] !== actual[0]) {
continue;
}
if (accept[1] === '*' || accept[1] === actual[1]) {
type = t;
break;
}
}
if (type === null) {
// not one of the allowed mimetypes
props.onError({
type: 'mimetype',
actual: file.type,
allowed: accept.slice(),
});
return;
}
if (file.size > type.maxSize) {
// the file is too big
props.onError({
type: 'size',
actual: file.size,
allowed: type.maxSize,
});
return;
}
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onloadend = async () => {
if (reader.result === null) {
// reading the file failed
props.onError({
type: 'read',
message: reader.error.message,
});
return;
}
props.onUpload(reader.result, type.extension, type.mimetype);
};
};
props.onStart();
handler(event);
// reset the value such that the onChange event will be triggered again
// if the same file gets selected again
event.target.value = null;
};
return (
<FormInlineButton component="label" {...other}>
{props.label}
<input accept={accept.join(',')} type="file" hidden onChange={handleUpload} />
</FormInlineButton>
);
}
UploadButton.defaultProps = {
label: '',
acceptTypes: [],
onError: function () {},
onUpload: function (data, extension) {},
};

View File

@ -1,5 +1,7 @@
import React from 'react';
import Helper from '../../helper';
function init(initialState) {
const state = {
...initialState,
@ -8,10 +10,14 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
stream = Helper.InitStream(stream);
skills = Helper.InitSkills(skills);
const mapping = {
global: [],
local: [],
filter: [],
};
return mapping;
@ -19,6 +25,8 @@ function createMapping(settings) {
function Coder(props) {
const settings = init(props.settings);
const stream = Helper.InitStream(props.stream);
const skills = Helper.InitSkills(props.skills);
const handleChange = (newSettings) => {
let automatic = false;
@ -27,7 +35,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
};
React.useEffect(() => {
@ -41,6 +49,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};
@ -50,12 +59,12 @@ const codecs = [];
const type = 'audio';
const hwaccel = false;
function defaults() {
function defaults(stream, skills) {
const settings = init({});
return {
settings: settings,
mapping: createMapping(settings),
mapping: createMapping(settings, stream, skills),
};
}

View File

@ -1,21 +1,22 @@
import * as AudioDefault from './audio/Default';
import * as AudioDefault from './audio/default';
import * as NVDEC from './video/NVDEC';
import * as H264MMAL from './video/H264MMAL';
import * as H264CUVID from './video/H264CUVID';
import * as HEVCCUVID from './video/HEVCCUVID';
import * as MJPEGCUVID from './video/MJPEGCUVID';
import * as MPEG1CUVID from './video/MPEG1CUVID';
import * as MPEG2CUVID from './video/MPEG2CUVID';
import * as MPEG2MMAL from './video/MPEG2MMAL';
import * as MPEG4CUVID from './video/MPEG4CUVID';
import * as MPEG4MMAL from './video/MPEG4MMAL';
import * as VC1CUVID from './video/VC1CUVID';
import * as VC1MMAL from './video/VC1MMAL';
import * as VideoDefault from './video/Default';
import * as VideoToolbox from './video/VideoToolbox';
import * as VP8CUVID from './video/VP8CUVID';
import * as VP9CUVID from './video/VP9CUVID';
import * as NVDEC from './video/nvdec';
import * as H264MMAL from './video/h264_mmal';
import * as H264CUVID from './video/h264_cuvid';
import * as HEVCCUVID from './video/hevc_cuvid';
import * as MJPEGCUVID from './video/mjpeg_cuvid';
import * as MPEG1CUVID from './video/mpeg1_cuvid';
import * as MPEG2CUVID from './video/mpeg2_cuvid';
import * as MPEG2MMAL from './video/mpeg2_mmal';
import * as MPEG4CUVID from './video/mpeg4_cuvid';
import * as MPEG4MMAL from './video/mpeg4_mmal';
import * as VC1CUVID from './video/vc1_cuvid';
import * as VC1MMAL from './video/vc1_mmal';
import * as VideoDefault from './video/default';
import * as VideoToolbox from './video/videotoolbox';
import * as VP8CUVID from './video/vp8_cuvid';
import * as VP9CUVID from './video/vp9_cuvid';
import * as AV1CUVID from './video/av1_cuvid';
class Registry {
constructor(type) {
@ -112,6 +113,7 @@ const videoRegistry = new Registry('video');
videoRegistry.Register(VideoDefault);
videoRegistry.Register(VideoToolbox);
videoRegistry.Register(AV1CUVID);
videoRegistry.Register(NVDEC);
videoRegistry.Register(H264MMAL);
videoRegistry.Register(H264CUVID);

Some files were not shown because too many files have changed in this diff Show More