Merge branch 'dev'
@ -1,3 +1,3 @@
|
||||
# RESTREAMER UI
|
||||
RELEASE=1.4.0
|
||||
NODE_IMAGE=node:18.9.0-alpine3.15
|
||||
RELEASE=1.5.0
|
||||
NODE_IMAGE=node:19.0-alpine3.16
|
||||
|
||||
37
CHANGELOG.md
@ -1,6 +1,33 @@
|
||||
# Restreamer-UI
|
||||
|
||||
### v1.3.0 > v1.4.0
|
||||
## v1.4.0 > v1.5.0
|
||||
|
||||
- Add changelog viewer
|
||||
- Add skills props to encoder and decoder components
|
||||
- Add fps_mode to x264, x265, vp9 encoder
|
||||
- Add scale filter to non-hwaccel encoders
|
||||
- Add PeerTube and Media Network to publication services (plattforms, software)
|
||||
- Add reset button to hide a player logo ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||
- Mod expands V4L2_M2M options (an unstable RPI 64bit encoder)
|
||||
- Mod indicates a faulty cache configuration
|
||||
- Mod switches to the improved SRT syntax (thx to SA Consulting)
|
||||
- Mod improves display of progress data
|
||||
- Mod removes deprecated param ocl - now ochl (ff5)
|
||||
- Mod simplifies the setup of Restreamer-to-Restreamer connections
|
||||
- Mod adds Istafeed.me as StreamKey service to Instagram's publishing service
|
||||
- Mod renames "Low delay" to "Low latency (buffer)" and set false as default (requires more feedback)
|
||||
- Del removes support for clappr player
|
||||
- Fix npm dependencies (security fixes)
|
||||
- Fix videojs-overlay logo size ([#431](https://github.com/datarhei/restreamer/issues/431))
|
||||
- Fix use of TLS for input from local RTMP server
|
||||
- Fix Icecast publication service settings (datarhei/restreamer#429)
|
||||
- Fix removes SRT bitstream on tee (OBS > RTMP > SRT is faulty)
|
||||
|
||||
Dependency:
|
||||
|
||||
- datarhei Core v16.11.0+
|
||||
|
||||
## v1.3.0 > v1.4.0
|
||||
|
||||
- Add email field for Let's Encrypt certification
|
||||
|
||||
@ -8,11 +35,11 @@ Dependency:
|
||||
|
||||
- datarhei Core v16.10.1+
|
||||
|
||||
### v1.2.0 > v1.3.0
|
||||
## v1.2.0 > v1.3.0
|
||||
|
||||
- Add dlive & Trovo publication services
|
||||
- Add low_delay option to processing (default: true)
|
||||
- Mod uses the ingest stream for publication (datarhei/restreamer#411)
|
||||
- Add dlive & Trovo publication services
|
||||
- Mod optimized DVR on DiskFS
|
||||
- Mod updates packages
|
||||
- Fix SRT bitstream on tee
|
||||
@ -25,7 +52,7 @@ Dependency:
|
||||
|
||||
- datarhei Core v16.10.0+
|
||||
|
||||
#### v1.1.0 > v1.2.0
|
||||
## v1.1.0 > v1.2.0
|
||||
|
||||
- Add allow writing HLS to disk
|
||||
- Add audio pan filter
|
||||
@ -56,7 +83,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))
|
||||
|
||||
17
Dockerfile
@ -1,5 +1,5 @@
|
||||
ARG NODE_IMAGE=node:18.6.0-alpine3.15
|
||||
ARG CADDY_IMAGE=caddy:2.5.2-alpine
|
||||
ARG NODE_IMAGE=node:19.0-alpine3.16
|
||||
ARG CADDY_IMAGE=caddy:2.6.2-alpine
|
||||
|
||||
FROM $NODE_IMAGE as builder
|
||||
|
||||
@ -13,13 +13,10 @@ 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 set version berry && \
|
||||
yarn config set httpTimeout 600000 && \
|
||||
yarn install && \
|
||||
yarn run build
|
||||
|
||||
FROM $CADDY_IMAGE
|
||||
|
||||
@ -30,4 +27,4 @@ WORKDIR /ui
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD [ "caddy", "run", "-config", "/ui/Caddyfile" ]
|
||||
CMD [ "caddy", "run", "--config", "/ui/Caddyfile" ]
|
||||
|
||||
18
README.md
@ -26,23 +26,5 @@ $ npm run i18n-extract:clean
|
||||
$ npm run i18n-compile
|
||||
```
|
||||
|
||||
### Known outdated dependencies
|
||||
Requires MUI 5.2+ & React 18 compatibility. Clappr-Player upgrade (or removal).
|
||||
|
||||
```sh
|
||||
@mui/material 5.1.1 → 5.9.0
|
||||
@mui/styles ^5.1.1 → ^5.9.0
|
||||
@testing-library/dom ^8.13.0 → ^8.16.0
|
||||
@testing-library/jest-dom ^4.2.4 → ^5.16.4
|
||||
@testing-library/react ^12.1.5 → ^13.3.0
|
||||
@testing-library/user-event ^13.5.0 → ^14.2.5
|
||||
eslint ^7.32.0 → ^8.19.0
|
||||
hls.js ^0.14.17 → ^1.1.5
|
||||
react ^17.0.2 → ^18.2.0
|
||||
react-dom ^17.0.2 → ^18.2.0
|
||||
react-scripts ^4.0.3 → ^5.0.1
|
||||
typescript ^3.9.7 → ^4.7.4
|
||||
```
|
||||
|
||||
## License
|
||||
See the [LICENSE](./LICENSE) file for licensing information.
|
||||
|
||||
81
package.json
@ -1,51 +1,50 @@
|
||||
{
|
||||
"name": "restreamer-ui",
|
||||
"version": "1.4.0",
|
||||
"bundle": "restreamer-v2.3.0",
|
||||
"version": "1.5.0",
|
||||
"bundle": "restreamer-v2.4.0",
|
||||
"private": false,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@auth0/auth0-spa-js": "^1.22.3",
|
||||
"@clappr/core": "^0.4.21",
|
||||
"@clappr/hlsjs-playback": "^0.6.0",
|
||||
"@clappr/plugins": "^0.4.16",
|
||||
"@clappr/stats-plugin": "^0.2.0",
|
||||
"@emotion/react": "^11.10.0",
|
||||
"@emotion/styled": "^11.10.0",
|
||||
"@fontsource/dosis": "^4.5.9",
|
||||
"@auth0/auth0-spa-js": "^2.0.0",
|
||||
"@babel/plugin-syntax-flow": "^7.18.6",
|
||||
"@babel/plugin-transform-react-jsx": "^7.19.0",
|
||||
"@emotion/react": "^11.10.5",
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fontsource/dosis": "^4.5.10",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.1.2",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.1.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.1.2",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/react-fontawesome": "^0.2.0",
|
||||
"@lingui/core": "^3.14.0",
|
||||
"@lingui/macro": "^3.14.0",
|
||||
"@lingui/react": "^3.14.0",
|
||||
"@mui/icons-material": "^5.10.2",
|
||||
"@mui/lab": "^5.0.0-alpha.96",
|
||||
"@mui/material": "5.1.1",
|
||||
"@mui/styles": "^5.1.1",
|
||||
"@testing-library/dom": "^8.13.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^12.1.5",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"@lingui/core": "^3.15.0",
|
||||
"@lingui/macro": "^3.15.0",
|
||||
"@lingui/react": "^3.15.0",
|
||||
"@mui/icons-material": "^5.10.9",
|
||||
"@mui/lab": "^5.0.0-alpha.107",
|
||||
"@mui/material": "^5.10.13",
|
||||
"@mui/styles": "^5.10.10",
|
||||
"@testing-library/dom": "^8.19.0",
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^18.0.25",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint": "^8.27.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": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-device-detect": "^2.2.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-scripts": "^4.0.3",
|
||||
"semver": "^7.3.7",
|
||||
"typescript": "^3.9.7",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"semver": "^7.3.8",
|
||||
"typescript": "^4.8.4",
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^8.3.2",
|
||||
"video.js": "^7.20.2",
|
||||
"uuid": "^9.0.0",
|
||||
"video.js": "^7.20.3",
|
||||
"videojs-overlay": "^2.1.5"
|
||||
},
|
||||
"scripts": {
|
||||
@ -73,7 +72,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 +81,12 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.13",
|
||||
"@lingui/cli": "^3.14.0",
|
||||
"@babel/core": "^7.20.2",
|
||||
"@lingui/cli": "^3.15.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"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": {}
|
||||
}
|
||||
|
||||
85
public/_player/clappr/dist/clappr.min.js
vendored
1
public/_player/clappr/dist/clappr.min.js.map
vendored
@ -1,4 +0,0 @@
|
||||
dist/clappr.min.js.map
|
||||
dist/clappr.min.js
|
||||
dist/clappr-stats.min.js
|
||||
dist/clappr-nerd-stats.min.js
|
||||
@ -1,112 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="{{description}}">
|
||||
<meta name="author" content="datarhei restreamer">
|
||||
<title>{{name}}</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico">
|
||||
<link rel="alternate" type="application/json+oembed" href="channels/{{channelid}}/oembed.json" title="{{name}}">
|
||||
<link rel="alternate" type="text/xml+oembed" href="channels/{{channelid}}/oembed.xml" title="{{name}}">
|
||||
<script src="channels/{{channelid}}/config.js"></script>
|
||||
<script src="player/clappr/dist/clappr.min.js"></script>
|
||||
<script src="player/clappr/dist/clappr-stats.min.js"></script>
|
||||
<script src="player/clappr/dist/clappr-nerd-stats.min.js"></script>
|
||||
<style>
|
||||
.player-poster[data-poster] .poster-background[data-poster] {
|
||||
height: initial !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="player" style="position:absolute;top:0;right:0;bottom:0;left:0"></div>
|
||||
<script>
|
||||
function getQueryParam(key, defaultValue) {
|
||||
var query = window.location.search.substring(1);
|
||||
var vars = query.split("&");
|
||||
for(var i = 0; i < vars.length; i++) {
|
||||
var pair = vars[i].split("=");
|
||||
if(pair[0] == key) {
|
||||
return pair[1];
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function convertBoolParam(key, defaultValue) {
|
||||
var val = getQueryParam(key, defaultValue);
|
||||
return val === true || val === "true" || val === "1" || val === "yes" || val === "on";
|
||||
}
|
||||
|
||||
function convertColorParam(parameter, defaultColor) {
|
||||
var re = new RegExp("^#([0-9a-f]{3}|[0-9a-f]{6})$");
|
||||
var c = getQueryParam(parameter, defaultColor);
|
||||
// decode color as # has to be represented by %23
|
||||
var c = decodeURIComponent(c);
|
||||
// if color was given without leading #, prepend it
|
||||
if (!String(c).startsWith("#")) c = "#" + c;
|
||||
|
||||
if (re.test(c)) {
|
||||
return c;
|
||||
} else {
|
||||
return defaultColor;
|
||||
}
|
||||
}
|
||||
|
||||
var autoplay = convertBoolParam("autoplay", playerConfig.autoplay);
|
||||
var mute = convertBoolParam("mute", playerConfig.mute);
|
||||
var statistics = convertBoolParam("stats", playerConfig.statistics);
|
||||
var color = convertColorParam("color", playerConfig.color.buttons);
|
||||
|
||||
var plugins = [];
|
||||
|
||||
if(statistics == true) {
|
||||
plugins.push(ClapprNerdStats);
|
||||
plugins.push(ClapprStats);
|
||||
}
|
||||
|
||||
var config = {
|
||||
source: playerConfig.source,
|
||||
parentId: '#player',
|
||||
baseUrl: 'clappr/',
|
||||
plugins: plugins,
|
||||
poster: playerConfig.poster + '?t=' + String(new Date().getTime()),
|
||||
mediacontrol: {
|
||||
seekbar: playerConfig.color.seekbar,
|
||||
buttons: color
|
||||
},
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
disableCanAutoPlay: true,
|
||||
autoPlay: autoplay,
|
||||
mute: mute,
|
||||
clapprStats: {
|
||||
runEach: 1000,
|
||||
onReport: (metrics) => {},
|
||||
},
|
||||
clapprNerdStats: {
|
||||
shortcut: ['command+shift+s', 'ctrl+shift+s'],
|
||||
iconPosition: 'top-right'
|
||||
}
|
||||
};
|
||||
|
||||
if(playerConfig.logo.image.length != 0) {
|
||||
config.watermark = playerConfig.logo.image;
|
||||
config.position = playerConfig.logo.position;
|
||||
|
||||
if(playerConfig.logo.link.length != 0) {
|
||||
config.watermarkLink = playerConfig.logo.link;
|
||||
}
|
||||
}
|
||||
|
||||
var player = new window.Clappr.Player(config);
|
||||
var posterPlugin = player.core.mediaControl.container.getPlugin('poster');
|
||||
player.on(window.Clappr.Events.PLAYER_STOP, function updatePoster () {
|
||||
posterPlugin.options.poster = playerConfig.poster + '?t=' + String(new Date().getTime());
|
||||
posterPlugin.render();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
10
public/_player/videojs/dist/video-js-skin.css
vendored
@ -123,13 +123,9 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.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-no-background > img, .vjs-public .vjs-overlay-no-background > a > img {
|
||||
max-width: 100%!important;
|
||||
max-height: calc(18vw);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-left {
|
||||
|
||||
@ -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: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-no-background>a>img,.vjs-public .vjs-overlay-no-background>img{max-width:100%!important;max-height:calc(18vw)}.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}
|
||||
@ -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();
|
||||
});
|
||||
@ -14,9 +14,15 @@ import ChannelList from './misc/ChannelList';
|
||||
import Footer from './Footer';
|
||||
import I18n from './I18n';
|
||||
import Header from './Header';
|
||||
import * as M from './utils/metadata';
|
||||
import Restreamer from './utils/restreamer';
|
||||
import Router from './Router';
|
||||
import Views from './views';
|
||||
import { UI as Version } from './version';
|
||||
import Changelog from './misc/Changelog';
|
||||
|
||||
import SemverGt from 'semver/functions/gt';
|
||||
import SemverValid from 'semver/functions/valid';
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
MainHeader: {
|
||||
@ -58,6 +64,12 @@ export default function RestreamerUI(props) {
|
||||
channelid: '',
|
||||
channels: [],
|
||||
});
|
||||
const [$metadata, setMetadata] = React.useState({});
|
||||
const [$changelog, setChangelog] = React.useState({
|
||||
open: false,
|
||||
current: '',
|
||||
previous: '',
|
||||
});
|
||||
|
||||
const restreamer = React.useRef(null);
|
||||
|
||||
@ -131,6 +143,8 @@ export default function RestreamerUI(props) {
|
||||
|
||||
const valid = await restreamer.current.Validate();
|
||||
|
||||
await checkChangelog();
|
||||
|
||||
setState({
|
||||
...$state,
|
||||
initialized: true,
|
||||
@ -146,8 +160,93 @@ export default function RestreamerUI(props) {
|
||||
setReady(true);
|
||||
};
|
||||
|
||||
const checkChangelog = async () => {
|
||||
let showChangelog = true;
|
||||
|
||||
if (restreamer.current.IsConnected() === true) {
|
||||
let metadata = await restreamer.current.GetMetadata(false);
|
||||
const channels = await restreamer.current.ListChannels();
|
||||
let current = Version.replace('restreamer-', '');
|
||||
let previous = '';
|
||||
|
||||
if (SemverValid(current) === null) {
|
||||
showChangelog = false;
|
||||
}
|
||||
|
||||
if (metadata === null) {
|
||||
if (channels.length === 1) {
|
||||
const progress = await restreamer.current.GetIngestProgress(channels[0].channelid);
|
||||
if (progress.valid === false) {
|
||||
// assume fresh installation
|
||||
metadata = M.initMetadata(metadata);
|
||||
await restreamer.current.SetMetadata({
|
||||
...metadata,
|
||||
bundle: {
|
||||
...metadata.bundle,
|
||||
version: current,
|
||||
},
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metadata = M.initMetadata(metadata);
|
||||
|
||||
if ('version' in metadata.bundle) {
|
||||
if (SemverValid(metadata.bundle.version) !== null) {
|
||||
previous = metadata.bundle.version;
|
||||
}
|
||||
}
|
||||
|
||||
if (showChangelog === true) {
|
||||
if (SemverValid(previous) === null) {
|
||||
previous = '';
|
||||
}
|
||||
|
||||
if (previous.length !== 0) {
|
||||
if (!SemverGt(current, previous)) {
|
||||
showChangelog = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setMetadata({
|
||||
...$metadata,
|
||||
...metadata,
|
||||
});
|
||||
|
||||
setChangelog({
|
||||
...$changelog,
|
||||
open: showChangelog,
|
||||
current: current,
|
||||
previous: previous,
|
||||
});
|
||||
}
|
||||
|
||||
return showChangelog;
|
||||
};
|
||||
|
||||
const handleCloseChangelog = async () => {
|
||||
await restreamer.current.SetMetadata({
|
||||
...$metadata,
|
||||
bundle: {
|
||||
...$metadata.bundle,
|
||||
version: $changelog.current,
|
||||
},
|
||||
});
|
||||
|
||||
setChangelog({
|
||||
...$changelog,
|
||||
open: false,
|
||||
});
|
||||
};
|
||||
|
||||
const handleLogin = async (username, password) => {
|
||||
const connected = await restreamer.current.Login(username, password);
|
||||
|
||||
await checkChangelog();
|
||||
|
||||
setState({
|
||||
...$state,
|
||||
connected: connected,
|
||||
@ -203,6 +302,7 @@ export default function RestreamerUI(props) {
|
||||
enable: true,
|
||||
},
|
||||
},
|
||||
version: 3,
|
||||
};
|
||||
|
||||
if (username.length !== 0) {
|
||||
@ -356,38 +456,42 @@ export default function RestreamerUI(props) {
|
||||
return null;
|
||||
};
|
||||
|
||||
let view = <Views.Initializing />;
|
||||
if ($state.valid === false) {
|
||||
view = <Views.Invalid address={restreamer.current.Address()} />;
|
||||
} else if ($state.connected === false) {
|
||||
view = (
|
||||
<Views.Login
|
||||
onLogin={handleLogin}
|
||||
auths={restreamer.current.Auths()}
|
||||
hasService={$state.service}
|
||||
address={restreamer.current.Address()}
|
||||
onAuth0={handleAuth0}
|
||||
/>
|
||||
);
|
||||
} else if ($state.compatibility.compatible === false) {
|
||||
if ($state.compatibility.core.compatible === false) {
|
||||
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
|
||||
} else if ($state.compatibility.ffmpeg.compatible === false) {
|
||||
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
|
||||
}
|
||||
} else if ($state.password === true) {
|
||||
view = (
|
||||
<Views.Password
|
||||
onReset={handlePasswordReset}
|
||||
username={restreamer.current.ConfigValue('api.auth.username')}
|
||||
usernameOverride={restreamer.current.ConfigOverrides('api.auth.username')}
|
||||
password={restreamer.current.ConfigValue('api.auth.password')}
|
||||
passwordOverride={restreamer.current.ConfigOverrides('api.auth.password')}
|
||||
/>
|
||||
);
|
||||
let view = null;
|
||||
if ($state.initialized === false) {
|
||||
view = <Views.Initializing />;
|
||||
} else {
|
||||
view = <Router restreamer={restreamer.current} />;
|
||||
resources = handleResources;
|
||||
if ($state.valid === false) {
|
||||
view = <Views.Invalid address={restreamer.current.Address()} />;
|
||||
} else if ($state.connected === false) {
|
||||
view = (
|
||||
<Views.Login
|
||||
onLogin={handleLogin}
|
||||
auths={restreamer.current.Auths()}
|
||||
hasService={$state.service}
|
||||
address={restreamer.current.Address()}
|
||||
onAuth0={handleAuth0}
|
||||
/>
|
||||
);
|
||||
} else if ($state.compatibility.compatible === false) {
|
||||
if ($state.compatibility.core.compatible === false) {
|
||||
view = <Views.Incompatible type="core" have={$state.compatibility.core.have} want={$state.compatibility.core.want} />;
|
||||
} else if ($state.compatibility.ffmpeg.compatible === false) {
|
||||
view = <Views.Incompatible type="ffmpeg" have={$state.compatibility.ffmpeg.have} want={$state.compatibility.ffmpeg.want} />;
|
||||
}
|
||||
} else if ($state.password === true) {
|
||||
view = (
|
||||
<Views.Password
|
||||
onReset={handlePasswordReset}
|
||||
username={restreamer.current.ConfigValue('api.auth.username')}
|
||||
usernameOverride={restreamer.current.ConfigOverrides('api.auth.username')}
|
||||
password={restreamer.current.ConfigValue('api.auth.password')}
|
||||
passwordOverride={restreamer.current.ConfigOverrides('api.auth.password')}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
view = <Router restreamer={restreamer.current} />;
|
||||
resources = handleResources;
|
||||
}
|
||||
}
|
||||
|
||||
const expand = $state.connected && $state.compatibility.compatible && !$state.password;
|
||||
@ -444,6 +548,9 @@ export default function RestreamerUI(props) {
|
||||
onState={handleStateChannel}
|
||||
/>
|
||||
)}
|
||||
{expand && $changelog.open && (
|
||||
<Changelog open={$changelog.open} onClose={handleCloseChangelog} current={$changelog.current} previous={$changelog.previous} />
|
||||
)}
|
||||
</NotifyProvider>
|
||||
</I18n>
|
||||
);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Route, Navigate, Routes, HashRouter } from 'react-router-dom';
|
||||
import { Route, Navigate, Routes, HashRouter as DOMRouter } from 'react-router-dom';
|
||||
|
||||
import Views from './views';
|
||||
|
||||
@ -11,9 +11,9 @@ export default function Router(props) {
|
||||
const channelid = props.restreamer.GetCurrentChannelID();
|
||||
|
||||
return (
|
||||
<HashRouter>
|
||||
<DOMRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Views.ChannelSelect restreamer={props.restreamer} />} />
|
||||
<Route path="/" element={<Views.ChannelSelect channelid={channelid} />} />
|
||||
<Route path="/playersite" element={<Views.Playersite restreamer={props.restreamer} />} />
|
||||
<Route path="/settings" element={<Views.Settings restreamer={props.restreamer} />} />
|
||||
<Route path="/settings/:tab" element={<Views.Settings restreamer={props.restreamer} />} />
|
||||
@ -26,7 +26,7 @@ export default function Router(props) {
|
||||
<Route path="/:channelid/publication/:service/:index" element={<Views.EditService key={channelid} restreamer={props.restreamer} />} />
|
||||
<Route path="*" render={() => <Navigate to="/" replace />} />
|
||||
</Routes>
|
||||
</HashRouter>
|
||||
</DOMRouter>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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';
|
||||
@ -16,12 +16,11 @@ 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>
|
||||
);
|
||||
|
||||
@ -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
@ -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: () => {},
|
||||
};
|
||||
@ -16,6 +16,15 @@ 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;
|
||||
@ -30,7 +39,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults();
|
||||
const defaults = c.defaults(props.skills);
|
||||
decoder.settings = defaults.settings;
|
||||
decoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -60,7 +69,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults({});
|
||||
const defaults = c.defaults(props.skills);
|
||||
encoder.settings = defaults.settings;
|
||||
encoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -116,10 +125,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 +139,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;
|
||||
}
|
||||
|
||||
@ -173,14 +182,14 @@ 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}
|
||||
@ -237,7 +246,6 @@ EncodingSelect.defaultProps = {
|
||||
streams: [],
|
||||
profile: {},
|
||||
codecs: [],
|
||||
availableEncoders: [],
|
||||
availableDecoders: [],
|
||||
skills: {},
|
||||
onChange: function (encoder, decoder, automatic) {},
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 8.9 KiB After Width: | Height: | Size: 6.8 KiB |
@ -1,101 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Plugins } from '@clappr/plugins';
|
||||
import Clappr from '@clappr/core';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import HLS from '@clappr/hlsjs-playback';
|
||||
//import ClapprStats from '@clappr/stats-plugin';
|
||||
|
||||
/*
|
||||
import Clappr from 'clappr';
|
||||
import ClapprStats from 'clappr-stats';
|
||||
*/
|
||||
|
||||
//import ClapprNerdStats from 'clappr-nerd-stats';
|
||||
|
||||
Clappr.Loader.registerPlayback(HLS);
|
||||
|
||||
for (let plugin of Object.values(Plugins)) {
|
||||
Clappr.Loader.registerPlugin(plugin);
|
||||
}
|
||||
|
||||
class Player extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.player = new Clappr.Player({});
|
||||
|
||||
this.playerRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.player.attachTo(this.playerRef.current);
|
||||
this.setConfig(this.props.config);
|
||||
this.setSource(this.props.source);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.player.destroy();
|
||||
}
|
||||
|
||||
shouldComponentUpdate(nextProps, _) {
|
||||
if (nextProps.source !== this.props.source) {
|
||||
this.setSource(nextProps.source);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
setSource(source) {
|
||||
this.player.load(source);
|
||||
}
|
||||
|
||||
setConfig(config) {
|
||||
delete config.source;
|
||||
delete config.sources;
|
||||
delete config.parent;
|
||||
delete config.parentid;
|
||||
|
||||
config = {
|
||||
plugins: [],
|
||||
...config,
|
||||
};
|
||||
|
||||
const plugins = config.plugins;
|
||||
config.plugins = [];
|
||||
|
||||
for (let p of plugins) {
|
||||
switch (p) {
|
||||
/*
|
||||
case 'ClapprStats':
|
||||
config.plugins.push(ClapprStats);
|
||||
break;
|
||||
*/
|
||||
/*
|
||||
case 'ClapprNerdStats':
|
||||
config.plugins.push(ClapprNerdStats);
|
||||
break;
|
||||
*/
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.player.configure(config);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
spacing={1}
|
||||
style={{ position: 'absolute', top: 0, left: 0, bottom: 0, right: 0 }}
|
||||
ref={this.playerRef}
|
||||
></Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Player;
|
||||
@ -1,10 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import Clappr from './clappr';
|
||||
import VideoJS from './videojs';
|
||||
|
||||
export default function Player(props) {
|
||||
const type = props.type ? props.type : 'clappr';
|
||||
const type = props.type ? props.type : 'videojs-internal';
|
||||
|
||||
if (type === 'videojs-internal' || type === 'videojs-public') {
|
||||
const config = {
|
||||
@ -16,7 +15,7 @@ export default function Player(props) {
|
||||
responsive: true,
|
||||
fluid: true,
|
||||
plugins: {
|
||||
reloadSourceOnError: {}
|
||||
reloadSourceOnError: {},
|
||||
},
|
||||
sources: [{ src: props.source, type: 'application/x-mpegURL' }],
|
||||
};
|
||||
@ -79,57 +78,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} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -123,13 +123,10 @@
|
||||
|
||||
/* overlay */
|
||||
|
||||
.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-no-background > img,
|
||||
.vjs-public .vjs-overlay-no-background > a > img {
|
||||
max-width: 100% !important;
|
||||
max-height: calc(18vw);
|
||||
}
|
||||
|
||||
.vjs-public .vjs-overlay-top-left {
|
||||
@ -161,7 +158,6 @@
|
||||
.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 {
|
||||
|
||||
124
src/misc/Player/video-js-skin-public.min.css
vendored
@ -1,123 +1 @@
|
||||
.vjs-public {
|
||||
--video-js--primary: #eaea05;
|
||||
}
|
||||
.vjs-public .vjs-big-play-button {
|
||||
width: 70px;
|
||||
height: 70px;
|
||||
background: 0 0;
|
||||
line-height: 180px;
|
||||
font-size: 180px;
|
||||
border: none;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -90px;
|
||||
margin-left: -90px;
|
||||
color: rgba(255, 255, 255, 0.65);
|
||||
}
|
||||
.vjs-public.vjs-big-play-button:focus,
|
||||
.vjs-public:hover .vjs-big-play-button {
|
||||
background-color: transparent;
|
||||
color: rgba(255, 255, 255, 1);
|
||||
}
|
||||
.vjs-public .vjs-control-bar {
|
||||
height: 70px;
|
||||
padding-top: 20px;
|
||||
background: 0 0;
|
||||
background-image: linear-gradient(0deg, rgba(0, 0, 0, 0.85), transparent);
|
||||
}
|
||||
.vjs-public .vjs-time-tooltip {
|
||||
z-index: 0;
|
||||
}
|
||||
.vjs-public .vjs-button > .vjs-icon-placeholder:before {
|
||||
line-height: 50px;
|
||||
}
|
||||
.vjs-public .vjs-play-progress:before {
|
||||
display: none;
|
||||
}
|
||||
.vjs-public .vjs-progress-control {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 15px;
|
||||
width: calc(100% - 30px);
|
||||
height: 20px;
|
||||
}
|
||||
.vjs-public .vjs-progress-control .vjs-progress-holder {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
.vjs-public .vjs-play-progress {
|
||||
background-color: var(--video-js--primary);
|
||||
}
|
||||
.vjs-public .vjs-slider {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.vjs-public .vjs-load-progress {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.vjs-public .vjs-load-progress div {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
.vjs-public .vjs-remaining-time {
|
||||
order: 0;
|
||||
line-height: 50px;
|
||||
flex: 3;
|
||||
text-align: left;
|
||||
}
|
||||
.vjs-public .vjs-live-control {
|
||||
line-height: 50px;
|
||||
}
|
||||
.vjs-public .vjs-volume-panel .vjs-volume-control.vjs-volume-horizontal {
|
||||
padding-top: 1em;
|
||||
}
|
||||
.vjs-public .vjs-control .vjs-volume-panel {
|
||||
width: 4.5em;
|
||||
}
|
||||
.vjs-public .vjs-live-display {
|
||||
margin-left: 1.8em;
|
||||
}
|
||||
.vjs-internal .vjs-subs-caps-button {
|
||||
display: none;
|
||||
}
|
||||
.vjs-public .vjs-custom-control-spacer {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
.vjs-public .vjs-overlay > a > img {
|
||||
width: 100%;
|
||||
}
|
||||
.vjs-public .vjs-overlay-no-background {
|
||||
max-width: 28% !important;
|
||||
max-height: 28% !important;
|
||||
}
|
||||
.vjs-public .vjs-overlay-top-left {
|
||||
top: 20px !important;
|
||||
left: 30px !important;
|
||||
}
|
||||
.vjs-public .vjs-overlay-top-right {
|
||||
top: 20px !important;
|
||||
right: 30px !important;
|
||||
}
|
||||
.vjs-public .vjs-overlay-bottom-left {
|
||||
bottom: 20px !important;
|
||||
left: 30px !important;
|
||||
}
|
||||
.vjs-public .vjs-overlay-bottom-right {
|
||||
bottom: 20px !important;
|
||||
right: 30px !important;
|
||||
}
|
||||
.vjs-public .vjs-license .vjs-menu .vjs-menu-content {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
.vjs-public .vjs-license-top-level-header {
|
||||
background: unset !important;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
|
||||
min-width: 100px;
|
||||
}
|
||||
.vjs-public .vjs-lock-open {
|
||||
z-index: 1000;
|
||||
}
|
||||
.vjs-public{--video-js--primary:#EAEA05}.vjs-public .vjs-big-play-button{width:70px;height:70px;background: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-no-background>a>img,.vjs-public .vjs-overlay-no-background>img{max-width:100%!important;max-height:calc(18vw)}.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}
|
||||
@ -57,7 +57,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={Math.round(progress.fps)} />
|
||||
<Number value={progress.fps} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
@ -70,7 +70,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 +83,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 +96,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 +108,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 +122,7 @@ export default function Progress(props) {
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h4">
|
||||
<strong>
|
||||
<Number value={Math.round(progress.dup)} />
|
||||
<Number value={progress.dup} digits={2} minDigits={2} />
|
||||
</strong>
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -41,6 +41,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -70,6 +70,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -40,6 +40,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -66,6 +66,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -66,6 +66,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -67,6 +67,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -133,6 +133,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -66,6 +66,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -218,6 +218,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -96,6 +96,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -1,15 +1,26 @@
|
||||
import React from 'react';
|
||||
import SemverSatisfies from 'semver/functions/satisfies';
|
||||
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
|
||||
import BoxText from '../../../BoxText';
|
||||
import TextField from '../../../TextField';
|
||||
import Video from '../../settings/Video';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
bitrate: '4096',
|
||||
fps: '25',
|
||||
gop: '2',
|
||||
gop: '4',
|
||||
profile: 'auto',
|
||||
force_key_frames: 'expr:gte(t,n_forced*1)',
|
||||
num_capture_buffers: '256',
|
||||
num_output_buffers: '512',
|
||||
fps_mode: 'cfr',
|
||||
|
||||
...initialState,
|
||||
};
|
||||
|
||||
@ -64,10 +75,20 @@ Codec Controls
|
||||
4: High
|
||||
*/
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, skills) {
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'h264_v4l2m2m',
|
||||
'-num_capture_buffers',
|
||||
`${settings.num_capture_buffers}`,
|
||||
'-num_output_buffers',
|
||||
`${settings.num_output_buffers}`,
|
||||
'-force_key_frames',
|
||||
`${settings.force_key_frames}`,
|
||||
'-b:v',
|
||||
`${settings.bitrate}k`,
|
||||
'-maxrate',
|
||||
@ -78,10 +99,22 @@ function createMapping(settings) {
|
||||
`${settings.fps}`,
|
||||
'-pix_fmt',
|
||||
'yuv420p',
|
||||
'-sc_threshold',
|
||||
'0',
|
||||
'-copyts',
|
||||
'-avoid_negative_ts',
|
||||
'disabled',
|
||||
'-max_muxing_queue_size',
|
||||
'2048',
|
||||
];
|
||||
|
||||
if (settings.gop !== 'auto') {
|
||||
local.push('-g', `${Math.round(parseInt(settings.fps) * parseInt(settings.gop)).toFixed(0)}`);
|
||||
local.push('-keyint_min', `${parseInt(settings.fps)}`);
|
||||
}
|
||||
|
||||
if (ffversion === 5) {
|
||||
local.push('-fps_mode', `${settings.fps_mode}`);
|
||||
}
|
||||
|
||||
if (settings.profile !== 'auto') {
|
||||
@ -98,6 +131,10 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -106,7 +143,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -125,6 +162,13 @@ function Coder(props) {
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<BoxText color="danger">
|
||||
<Trans>V4L2_M2M is experimental.</Trans>
|
||||
<br />
|
||||
<Trans>We recommend OpenMAX IL for Raspberry PI (3/4) with a 32-bit operating system.</Trans>
|
||||
</BoxText>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
@ -133,6 +177,23 @@ function Coder(props) {
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||
<Typography variant="caption">
|
||||
<Trans>To stabilize the system, increase the HLS segment length for the keyframe interval by 2-3 * (Processing and Control).</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{ffversion === 5 && (
|
||||
<Grid item xs={12}>
|
||||
<Video.FpsMode value={settings.fps_mode} onChange={update('fps_mode')} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<TextField label={<Trans>Force key frames</Trans>} type="text" value={settings.force_key_frames} onChange={update('force_key_frames')} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField label={<Trans>Capture buffer</Trans>} type="number" value={settings.num_capture_buffers} onChange={update('num_capture_buffers')} />
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<TextField label={<Trans>Output buffer</Trans>} type="number" value={settings.num_output_buffers} onChange={update('num_output_buffers')} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
@ -141,6 +202,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
@ -154,12 +216,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -149,6 +149,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -125,6 +125,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -149,6 +149,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -36,6 +36,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import SemverSatisfies from 'semver/functions/satisfies';
|
||||
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
@ -9,13 +10,19 @@ function init(initialState) {
|
||||
bitrate: '4096',
|
||||
fps: '25',
|
||||
gop: '2',
|
||||
fps_mode: 'auto',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, skills) {
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'libvpx-vp9',
|
||||
@ -42,6 +49,10 @@ function createMapping(settings) {
|
||||
);
|
||||
}
|
||||
|
||||
if (ffversion === 5) {
|
||||
local.push('-fps_mode', `${settings.fps_mode}`);
|
||||
}
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: local,
|
||||
@ -52,6 +63,10 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -60,7 +75,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -82,12 +97,17 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.Framerate value={settings.fps} onChange={update('fps')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||
</Grid>
|
||||
{ffversion === 5 && (
|
||||
<Grid item xs={12}>
|
||||
<Video.FpsMode value={settings.fps_mode} onChange={update('fps_mode')} />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
@ -95,6 +115,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
@ -108,12 +129,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -149,6 +149,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import SemverSatisfies from 'semver/functions/satisfies';
|
||||
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
@ -16,13 +17,19 @@ function init(initialState) {
|
||||
gop: '2',
|
||||
profile: 'auto',
|
||||
tune: 'zerolatency',
|
||||
fps_mode: 'auto',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, skills) {
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'libx264',
|
||||
@ -51,6 +58,10 @@ function createMapping(settings) {
|
||||
);
|
||||
}
|
||||
|
||||
if (ffversion === 5) {
|
||||
local.push('-fps_mode', `${settings.fps_mode}`);
|
||||
}
|
||||
|
||||
if (settings.profile !== 'auto') {
|
||||
local.push('-profile:v', `${settings.profile}`);
|
||||
}
|
||||
@ -109,6 +120,10 @@ Tune.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -117,7 +132,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -139,12 +154,17 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.Framerate value={settings.fps} onChange={update('fps')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||
</Grid>
|
||||
{ffversion === 5 && (
|
||||
<Grid item xs={12}>
|
||||
<Video.FpsMode value={settings.fps_mode} onChange={update('fps_mode')} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={6}>
|
||||
<Preset value={settings.preset} onChange={update('preset')} />
|
||||
</Grid>
|
||||
@ -161,6 +181,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
@ -174,12 +195,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import SemverSatisfies from 'semver/functions/satisfies';
|
||||
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
@ -16,13 +17,19 @@ function init(initialState) {
|
||||
gop: '2',
|
||||
profile: 'auto',
|
||||
tune: 'zerolatency',
|
||||
fps_mode: 'auto',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, skills) {
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'libx265',
|
||||
@ -51,6 +58,10 @@ function createMapping(settings) {
|
||||
);
|
||||
}
|
||||
|
||||
if (ffversion === 5) {
|
||||
local.push('-fps_mode', `${settings.fps_mode}`);
|
||||
}
|
||||
|
||||
if (settings.profile !== 'auto') {
|
||||
local.push('-profile:v', `${settings.profile}`);
|
||||
}
|
||||
@ -109,6 +120,10 @@ Tune.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -117,7 +132,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -139,12 +154,17 @@ function Coder(props) {
|
||||
<Grid item xs={12}>
|
||||
<Video.Bitrate value={settings.bitrate} onChange={update('bitrate')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.Framerate value={settings.fps} onChange={update('fps')} allowCustom />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Video.GOP value={settings.gop} onChange={update('gop')} allowAuto allowCustom />
|
||||
</Grid>
|
||||
{ffversion === 5 && (
|
||||
<Grid item xs={12}>
|
||||
<Video.FpsMode value={settings.fps_mode} onChange={update('fps_mode')} />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={6}>
|
||||
<Preset value={settings.preset} onChange={update('preset')} />
|
||||
</Grid>
|
||||
@ -161,6 +181,7 @@ function Coder(props) {
|
||||
Coder.defaultProps = {
|
||||
stream: {},
|
||||
settings: {},
|
||||
skills: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
@ -174,12 +195,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -203,6 +203,91 @@ Size.defaultProps = {
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Height(props) {
|
||||
const { i18n } = useLingui();
|
||||
const height = [
|
||||
{ value: '4320', label: '4320' },
|
||||
{ value: '2880', label: '2880' },
|
||||
{ value: '2160', label: '2160' },
|
||||
{ value: '1800', label: '1800' },
|
||||
{ value: '1600', label: '1600' },
|
||||
{ value: '1440', label: '1440' },
|
||||
{ value: '1080', label: '1080' },
|
||||
{ value: '900', label: '900' },
|
||||
{ value: '720', label: '720' },
|
||||
{ value: '540', label: '540' },
|
||||
{ value: '360', label: '360' },
|
||||
];
|
||||
|
||||
if (props.allowCustom === true) {
|
||||
height.push({ value: 'custom', label: i18n._(t`Custom ...`) });
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectCustom
|
||||
options={height}
|
||||
label={props.label}
|
||||
customLabel={props.customLabel}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
variant={props.variant}
|
||||
allowCustom={props.allowCustom}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Height.defaultProps = {
|
||||
allowNone: false,
|
||||
allowCustom: false,
|
||||
variant: 'outlined',
|
||||
label: <Trans>Height</Trans>,
|
||||
customLabel: <Trans>Custom size</Trans>,
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Width(props) {
|
||||
const { i18n } = useLingui();
|
||||
const width = [
|
||||
{ value: '7680', label: '7680' },
|
||||
{ value: '5120', label: '5120' },
|
||||
{ value: '4096', label: '4096' },
|
||||
{ value: '3840', label: '3840' },
|
||||
{ value: '3200', label: '3200' },
|
||||
{ value: '2560', label: '2560' },
|
||||
{ value: '2048', label: '2048' },
|
||||
{ value: '1920', label: '1920' },
|
||||
{ value: '1600', label: '1600' },
|
||||
{ value: '1280', label: '1280' },
|
||||
{ value: '960', label: '960' },
|
||||
{ value: '640', label: '640' },
|
||||
];
|
||||
|
||||
if (props.allowCustom === true) {
|
||||
width.push({ value: 'custom', label: i18n._(t`Custom ...`) });
|
||||
}
|
||||
|
||||
return (
|
||||
<SelectCustom
|
||||
options={width}
|
||||
label={props.label}
|
||||
customLabel={props.customLabel}
|
||||
value={props.value}
|
||||
onChange={props.onChange}
|
||||
variant={props.variant}
|
||||
allowCustom={props.allowCustom}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Width.defaultProps = {
|
||||
allowNone: false,
|
||||
allowCustom: false,
|
||||
variant: 'outlined',
|
||||
label: <Trans>Width</Trans>,
|
||||
customLabel: <Trans>Custom size</Trans>,
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Format(props) {
|
||||
const { i18n } = useLingui();
|
||||
const sizes = [
|
||||
@ -242,11 +327,38 @@ Format.defaultProps = {
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function FpsMode(props) {
|
||||
return (
|
||||
<Select label={<Trans>Framerate mode</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="passthrough">
|
||||
<Trans>Frame is passed through (Passthrough)</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="cfr">
|
||||
<Trans>Constant frame rate (CFR)</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="vfr">
|
||||
<Trans>Variable frame rate (VFR)</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="auto">
|
||||
<Trans>Choose between CFR and VFR (Auto)</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
FpsMode.defaultProps = {
|
||||
value: '',
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
export default {
|
||||
Bitrate,
|
||||
GOP,
|
||||
Framerate,
|
||||
Profile,
|
||||
Size,
|
||||
Width,
|
||||
Height,
|
||||
Format,
|
||||
FpsMode,
|
||||
};
|
||||
|
||||
|
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 5.3 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 6.2 KiB |
@ -1,155 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://web.resource.org/cc/"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="120"
|
||||
height="42"
|
||||
id="svg2759"
|
||||
sodipodi:version="0.32"
|
||||
inkscape:version="0.45+devel"
|
||||
version="1.0"
|
||||
sodipodi:docname="by.svg"
|
||||
inkscape:output_extension="org.inkscape.output.svg.inkscape">
|
||||
<defs
|
||||
id="defs2761" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#8b8b8b"
|
||||
borderopacity="1"
|
||||
gridtolerance="10000"
|
||||
guidetolerance="10"
|
||||
objecttolerance="10"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1"
|
||||
inkscape:cx="179"
|
||||
inkscape:cy="89.569904"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
width="120px"
|
||||
height="42px"
|
||||
inkscape:showpageshadow="false"
|
||||
inkscape:window-width="1198"
|
||||
inkscape:window-height="624"
|
||||
inkscape:window-x="396"
|
||||
inkscape:window-y="242" />
|
||||
<metadata
|
||||
id="metadata2764">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<g
|
||||
transform="matrix(0.9937728,0,0,0.9936696,-177.69267,6.25128e-7)"
|
||||
id="g260"
|
||||
inkscape:export-filename="/mnt/hgfs/Bov/Documents/Work/2007/cc/identity/srr buttons/big/by.png"
|
||||
inkscape:export-xdpi="300.23013"
|
||||
inkscape:export-ydpi="300.23013">
|
||||
<path
|
||||
id="path3817_1_"
|
||||
nodetypes="ccccccc"
|
||||
d="M 181.96579,0.51074 L 296.02975,0.71338 C 297.6235,0.71338 299.04733,0.47705 299.04733,3.89404 L 298.90768,41.46093 L 179.08737,41.46093 L 179.08737,3.75439 C 179.08737,2.06934 179.25046,0.51074 181.96579,0.51074 z"
|
||||
style="fill:#aab2ab" />
|
||||
|
||||
<path
|
||||
d="M 297.29636,0 L 181.06736,0 C 179.82078,0 178.80613,1.01416 178.80613,2.26074 L 178.80613,41.75732 C 178.80613,42.03906 179.03513,42.26757 179.31687,42.26757 L 299.04734,42.26757 C 299.32908,42.26757 299.55808,42.03905 299.55808,41.75732 L 299.55808,2.26074 C 299.55807,1.01416 298.54343,0 297.29636,0 z M 181.06735,1.02148 L 297.29635,1.02148 C 297.98043,1.02148 298.53658,1.57714 298.53658,2.26074 C 298.53658,2.26074 298.53658,18.20898 298.53658,29.71045 L 215.19234,29.71045 C 212.14742,35.21631 206.28121,38.95459 199.54879,38.95459 C 192.81344,38.95459 186.94869,35.21973 183.90524,29.71045 L 179.8276,29.71045 C 179.8276,18.20899 179.8276,2.26074 179.8276,2.26074 C 179.82761,1.57715 180.38376,1.02148 181.06735,1.02148 z"
|
||||
id="path263" />
|
||||
|
||||
<g
|
||||
enable-background="new "
|
||||
id="g265">
|
||||
<path
|
||||
d="M 253.07761,32.95605 C 253.39499,32.95605 253.68503,32.98437 253.94773,33.04003 C 254.20945,33.09569 254.43308,33.18749 254.62058,33.31542 C 254.8071,33.44237 254.95261,33.6123 255.05515,33.82323 C 255.15769,34.03514 255.20945,34.29589 255.20945,34.60741 C 255.20945,34.94335 255.13328,35.22264 254.97996,35.44628 C 254.82762,35.67089 254.60105,35.85351 254.30223,35.99706 C 254.71434,36.11522 255.02196,36.32226 255.22508,36.61815 C 255.4282,36.91404 255.52977,37.27049 255.52977,37.68749 C 255.52977,38.02343 255.46434,38.31444 255.33348,38.56054 C 255.20262,38.80566 255.02586,39.00683 254.80516,39.1621 C 254.58348,39.31835 254.33055,39.43358 254.04735,39.5078 C 253.76317,39.583 253.47215,39.6201 253.17235,39.6201 L 249.936,39.6201 L 249.936,32.95604 L 253.07761,32.95604 L 253.07761,32.95605 z M 252.89011,35.65137 C 253.15183,35.65137 253.36667,35.58887 253.53562,35.46485 C 253.70359,35.34083 253.78757,35.13965 253.78757,34.86036 C 253.78757,34.70509 253.75925,34.57716 253.70359,34.47852 C 253.64695,34.37891 253.57273,34.30176 253.47898,34.24512 C 253.38523,34.18946 253.27781,34.15039 253.15671,34.12891 C 253.03561,34.10743 252.90866,34.09668 252.77878,34.09668 L 251.40476,34.09668 L 251.40476,35.65137 L 252.89011,35.65137 z M 252.97604,38.47949 C 253.11959,38.47949 253.25631,38.46582 253.38717,38.4375 C 253.51803,38.40918 253.63326,38.3623 253.73385,38.29785 C 253.83346,38.23242 253.91256,38.14355 253.97213,38.03125 C 254.0317,37.91992 254.061,37.77637 254.061,37.60254 C 254.061,37.26074 253.96432,37.0166 253.77096,36.87012 C 253.5776,36.72461 253.32174,36.65137 253.00436,36.65137 L 251.40475,36.65137 L 251.40475,38.47949 L 252.97604,38.47949 z"
|
||||
id="path267"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
<path
|
||||
d="M 255.78854,32.95605 L 257.43209,32.95605 L 258.99264,35.58789 L 260.54342,32.95605 L 262.17721,32.95605 L 259.70358,37.0625 L 259.70358,39.62012 L 258.23483,39.62012 L 258.23483,37.02539 L 255.78854,32.95605 z"
|
||||
id="path269"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
</g>
|
||||
|
||||
<g
|
||||
id="g5908_1_"
|
||||
transform="matrix(0.872921,0,0,0.872921,50.12536,143.2144)">
|
||||
|
||||
<path
|
||||
id="path5906_1_"
|
||||
cx="296.35416"
|
||||
ry="22.939548"
|
||||
cy="264.3577"
|
||||
type="arc"
|
||||
rx="22.939548"
|
||||
d="M 186.90065,-141.46002 C 186.90623,-132.77923 179.87279,-125.73852 171.19257,-125.73291 C 162.51235,-125.72736 155.47051,-132.76025 155.46547,-141.44098 C 155.46547,-141.44714 155.46547,-141.45331 155.46547,-141.46002 C 155.46043,-150.14081 162.49333,-157.18152 171.17355,-157.18658 C 179.8549,-157.19213 186.89561,-150.15924 186.90065,-141.47845 C 186.90065,-141.4729 186.90065,-141.46619 186.90065,-141.46002 z"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
<g
|
||||
id="g5706_1_"
|
||||
transform="translate(-289.6157,99.0653)">
|
||||
<path
|
||||
id="path5708_1_"
|
||||
d="M 473.57574,-253.32751 C 477.06115,-249.8421 478.80413,-245.5736 478.80413,-240.52532 C 478.80413,-235.47594 477.09136,-231.25329 473.66582,-227.85741 C 470.03051,-224.28081 465.734,-222.49309 460.77635,-222.49309 C 455.87858,-222.49309 451.65648,-224.26628 448.11122,-227.81261 C 444.56541,-231.35845 442.79277,-235.59563 442.79277,-240.52532 C 442.79277,-245.45391 444.56541,-249.7213 448.11122,-253.32751 C 451.56642,-256.81402 455.7885,-258.557 460.77635,-258.557 C 465.82465,-258.55701 470.09039,-256.81403 473.57574,-253.32751 z M 450.45776,-250.98267 C 447.51104,-248.00629 446.03823,-244.51978 446.03823,-240.52033 C 446.03823,-236.52198 447.49651,-233.06507 450.41247,-230.14966 C 453.32897,-227.23316 456.80096,-225.77545 460.82952,-225.77545 C 464.85808,-225.77545 468.35967,-227.24768 471.33605,-230.19385 C 474.16198,-232.9303 475.57549,-236.37091 475.57549,-240.52033 C 475.57549,-244.63837 474.13903,-248.13379 471.26781,-251.00501 C 468.39714,-253.87568 464.9179,-255.31159 460.82952,-255.31159 C 456.74112,-255.31158 453.28314,-253.86841 450.45776,-250.98267 z M 458.21225,-242.27948 C 457.76196,-243.26117 457.08795,-243.75232 456.18903,-243.75232 C 454.59986,-243.75232 453.80558,-242.68225 453.80558,-240.54321 C 453.80558,-238.40368 454.59986,-237.33471 456.18903,-237.33471 C 457.23841,-237.33471 457.98795,-237.85546 458.43769,-238.89922 L 460.64045,-237.72625 C 459.59052,-235.86077 458.01536,-234.92779 455.91496,-234.92779 C 454.29506,-234.92779 452.99733,-235.42449 452.0229,-236.4168 C 451.0468,-237.41021 450.56016,-238.77953 450.56016,-240.52532 C 450.56016,-242.24035 451.06245,-243.60186 452.06764,-244.61034 C 453.07283,-245.61888 454.32466,-246.12291 455.82545,-246.12291 C 458.04557,-246.12291 459.63526,-245.24803 460.59626,-243.50005 L 458.21225,-242.27948 z M 468.57562,-242.27948 C 468.12475,-243.26117 467.46417,-243.75232 466.5932,-243.75232 C 464.97217,-243.75232 464.16107,-242.68225 464.16107,-240.54321 C 464.16107,-238.40368 464.97217,-237.33471 466.5932,-237.33471 C 467.64429,-237.33471 468.38037,-237.85546 468.80048,-238.89922 L 471.05249,-237.72625 C 470.00421,-235.86077 468.43127,-234.92779 466.33478,-234.92779 C 464.7171,-234.92779 463.42218,-235.42449 462.44831,-236.4168 C 461.47614,-237.41021 460.98896,-238.77953 460.98896,-240.52532 C 460.98896,-242.24035 461.48341,-243.60186 462.47181,-244.61034 C 463.45966,-245.61888 464.71711,-246.12291 466.24531,-246.12291 C 468.4615,-246.12291 470.04896,-245.24803 471.0066,-243.50005 L 468.57562,-242.27948 z" />
|
||||
|
||||
</g>
|
||||
|
||||
</g>
|
||||
|
||||
<g
|
||||
id="g275">
|
||||
<circle
|
||||
cx="255.55124"
|
||||
cy="15.31348"
|
||||
r="10.80664"
|
||||
id="circle277"
|
||||
sodipodi:cx="255.55124"
|
||||
sodipodi:cy="15.31348"
|
||||
sodipodi:rx="10.80664"
|
||||
sodipodi:ry="10.80664"
|
||||
style="fill:#ffffff" />
|
||||
|
||||
<g
|
||||
id="g279">
|
||||
<path
|
||||
d="M 258.67819,12.18701 C 258.67819,11.77051 258.3403,11.4331 257.92526,11.4331 L 253.15182,11.4331 C 252.73678,11.4331 252.39889,11.7705 252.39889,12.18701 L 252.39889,16.95996 L 253.72994,16.95996 L 253.72994,22.61182 L 257.34713,22.61182 L 257.34713,16.95996 L 258.67818,16.95996 L 258.67818,12.18701 L 258.67819,12.18701 z"
|
||||
id="path281" />
|
||||
|
||||
<circle
|
||||
cx="255.53854"
|
||||
cy="9.1723604"
|
||||
r="1.63281"
|
||||
id="circle283"
|
||||
sodipodi:cx="255.53854"
|
||||
sodipodi:cy="9.1723604"
|
||||
sodipodi:rx="1.63281"
|
||||
sodipodi:ry="1.63281" />
|
||||
|
||||
</g>
|
||||
|
||||
<path
|
||||
clip-rule="evenodd"
|
||||
d="M 255.5239,3.40723 C 252.29148,3.40723 249.55515,4.53516 247.31589,6.79102 C 245.01804,9.12452 243.8696,11.88672 243.8696,15.07569 C 243.8696,18.26466 245.01804,21.00733 247.31589,23.30225 C 249.61374,25.59668 252.35007,26.74414 255.5239,26.74414 C 258.73679,26.74414 261.52195,25.58789 263.87742,23.27295 C 266.09715,21.07568 267.2075,18.34326 267.2075,15.07568 C 267.2075,11.8081 266.07762,9.04687 263.8198,6.79101 C 261.56003,4.53516 258.79538,3.40723 255.5239,3.40723 z M 255.55319,5.50684 C 258.20163,5.50684 260.45065,6.44092 262.30026,8.30811 C 264.1694,10.15528 265.10397,12.41114 265.10397,15.07569 C 265.10397,17.75928 264.18893,19.98633 262.35885,21.75587 C 260.43014,23.66212 258.16256,24.61476 255.55319,24.61476 C 252.94284,24.61476 250.69381,23.67189 248.80612,21.78517 C 246.91647,19.89845 245.97311,17.66212 245.97311,15.0757 C 245.97311,12.48879 246.92721,10.23341 248.83541,8.30812 C 250.6655,6.44092 252.90475,5.50684 255.55319,5.50684 z"
|
||||
id="path285"
|
||||
style="fill-rule:evenodd" />
|
||||
|
||||
</g>
|
||||
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="120" height="42" version="1.0"><path d="M181.966.51 296.03.714c1.594 0 3.017-.236 3.017 3.181l-.14 37.567h-119.82V3.754c0-1.685.163-3.243 2.879-3.243z" style="fill:#aab2ab" transform="matrix(.99377 0 0 .99367 -177.693 0)"/><path d="M117.752 0H2.247A2.25 2.25 0 0 0 0 2.246v39.247c0 .28.228.507.508.507h118.984c.28 0 .508-.227.508-.507V2.246A2.25 2.25 0 0 0 117.752 0zM2.247 1.015h115.505c.68 0 1.233.552 1.233 1.231v27.276H36.16c-3.026 5.471-8.856 9.186-15.547 9.186-6.693 0-12.521-3.711-15.546-9.186H1.015V2.246c0-.679.553-1.231 1.232-1.231z"/><path d="M253.078 32.956c.317 0 .607.028.87.084.261.056.485.147.673.275.186.127.332.297.434.508.103.212.154.473.154.784 0 .336-.076.616-.229.84-.152.224-.379.407-.678.55.412.118.72.325.923.621.203.296.305.652.305 1.07 0 .335-.066.626-.197.873-.13.245-.307.446-.528.601a2.327 2.327 0 0 1-.758.346 3.402 3.402 0 0 1-.875.112h-3.236v-6.664h3.142zm-.188 2.695c.262 0 .477-.062.646-.186.168-.124.252-.325.252-.605a.773.773 0 0 0-.084-.381.63.63 0 0 0-.225-.234.951.951 0 0 0-.322-.116 2.169 2.169 0 0 0-.378-.032h-1.374v1.554h1.485zm.086 2.828c.144 0 .28-.013.411-.041.131-.029.246-.076.347-.14a.709.709 0 0 0 .238-.267.907.907 0 0 0 .089-.428c0-.342-.097-.586-.29-.733-.193-.145-.45-.219-.767-.219h-1.6v1.828h1.572zM255.789 32.956h1.643l1.56 2.632 1.551-2.632h1.634l-2.473 4.106v2.558h-1.47v-2.595l-2.445-4.069z" style="fill:#fff" transform="matrix(.99377 0 0 .99367 -177.693 0)"/><path d="M186.9-141.46c.006 8.68-7.027 15.721-15.707 15.727-8.68.006-15.722-7.027-15.728-15.708v-.019c-.005-8.68 7.028-15.722 15.709-15.727 8.68-.005 15.722 7.028 15.727 15.709v.018z" style="fill:#fff" transform="matrix(.86749 0 0 .8674 -127.88 142.308)"/><path d="M31.703 8.502c3.024 3.023 4.536 6.725 4.536 11.104 0 4.38-1.486 8.043-4.458 10.988-3.153 3.102-6.88 4.653-11.181 4.653-4.249 0-7.911-1.538-10.987-4.614C6.537 27.557 5 23.883 5 19.606c0-4.275 1.538-7.976 4.614-11.104 2.997-3.025 6.66-4.537 10.987-4.537 4.38 0 8.08 1.512 11.103 4.537zM11.65 10.535c-2.557 2.582-3.834 5.606-3.834 9.075 0 3.469 1.265 6.467 3.794 8.996 2.53 2.53 5.542 3.794 9.037 3.794 3.495 0 6.532-1.277 9.114-3.832 2.452-2.374 3.678-5.358 3.678-8.958 0-3.572-1.246-6.603-3.737-9.094-2.49-2.49-5.508-3.735-9.055-3.735-3.547 0-6.546 1.251-8.997 3.754zm6.727 7.55c-.391-.852-.976-1.278-1.756-1.278-1.378 0-2.067.928-2.067 2.784 0 1.855.689 2.783 2.067 2.783.91 0 1.561-.452 1.951-1.357l1.911 1.017c-.91 1.618-2.277 2.427-4.1 2.427-1.405 0-2.53-.43-3.376-1.291-.846-.862-1.269-2.05-1.269-3.564 0-1.488.436-2.669 1.308-3.543.872-.875 1.958-1.312 3.26-1.312 1.926 0 3.305.759 4.139 2.275l-2.068 1.059zm8.99 0c-.392-.852-.965-1.278-1.72-1.278-1.406 0-2.11.928-2.11 2.784 0 1.855.704 2.783 2.11 2.783.912 0 1.55-.452 1.915-1.357l1.953 1.017c-.91 1.618-2.274 2.427-4.092 2.427-1.404 0-2.527-.43-3.372-1.291-.843-.862-1.266-2.05-1.266-3.564 0-1.488.43-2.669 1.287-3.543.857-.875 1.947-1.312 3.273-1.312 1.923 0 3.3.759 4.13 2.275l-2.108 1.059z"/><g transform="matrix(.99377 0 0 .99367 -177.693 0)"><circle cx="255.551" cy="15.313" r="10.807" style="fill:#fff"/><path d="M258.678 12.187a.754.754 0 0 0-.753-.754h-4.773a.754.754 0 0 0-.753.754v4.773h1.33v5.652h3.618V16.96h1.331v-4.773z"/><circle cx="255.539" cy="9.172" r="1.633"/><path d="M255.524 3.407c-3.233 0-5.969 1.128-8.208 3.384-2.298 2.334-3.446 5.096-3.446 8.285s1.148 5.931 3.446 8.226c2.298 2.295 5.034 3.442 8.208 3.442 3.213 0 5.998-1.156 8.353-3.471 2.22-2.197 3.33-4.93 3.33-8.197 0-3.268-1.13-6.03-3.387-8.285-2.26-2.256-5.025-3.384-8.296-3.384zm.03 2.1c2.648 0 4.897.934 6.746 2.801 1.87 1.847 2.804 4.103 2.804 6.768 0 2.683-.915 4.91-2.745 6.68-1.929 1.906-4.196 2.859-6.806 2.859-2.61 0-4.86-.943-6.747-2.83-1.89-1.887-2.833-4.123-2.833-6.71 0-2.586.954-4.842 2.862-6.767 1.83-1.867 4.07-2.801 6.718-2.801z" clip-rule="evenodd" style="fill-rule:evenodd"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 6.0 KiB |
@ -13,7 +13,7 @@ function init(settings) {
|
||||
reconnect: true,
|
||||
delay: 30,
|
||||
staleTimeout: 30,
|
||||
low_delay: true,
|
||||
low_delay: false,
|
||||
...settings,
|
||||
};
|
||||
|
||||
@ -45,7 +45,7 @@ export default function Control(props) {
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox label={<Trans>Reconnect</Trans>} checked={settings.reconnect} onChange={handleChange('reconnect')} />
|
||||
<Checkbox label={<Trans>Low delay</Trans>} checked={settings.low_delay} onChange={handleChange('low_delay')} />
|
||||
<Checkbox label={<Trans>Low latency (Buffer)</Trans>} checked={settings.low_delay} onChange={handleChange('low_delay')} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
|
||||
@ -34,7 +34,7 @@ function createGraph(settings) {
|
||||
}
|
||||
|
||||
if (layout !== 'inherit') {
|
||||
mapping.push(`ocl=${layout}`);
|
||||
mapping.push(`ochl=${layout}`);
|
||||
}
|
||||
|
||||
if (mapping.length === 0) {
|
||||
|
||||
@ -5,6 +5,7 @@ import * as Volume from './audio/Volume';
|
||||
import * as Loudnorm from './audio/Loudnorm';
|
||||
|
||||
// Video Filter
|
||||
import * as Scale from './video/Scale';
|
||||
import * as Transpose from './video/Transpose';
|
||||
import * as HFlip from './video/HFlip';
|
||||
import * as VFlip from './video/VFlip';
|
||||
@ -51,6 +52,7 @@ audioRegistry.Register(Loudnorm);
|
||||
|
||||
// Video Filters
|
||||
const videoRegistry = new Registry('video');
|
||||
videoRegistry.Register(Scale);
|
||||
videoRegistry.Register(Transpose);
|
||||
videoRegistry.Register(HFlip);
|
||||
videoRegistry.Register(VFlip);
|
||||
|
||||
145
src/misc/filters/video/Scale.js
Normal file
@ -0,0 +1,145 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
import Select from '../../Select';
|
||||
import Video from '../../coders/settings/Video';
|
||||
|
||||
// Scale Filter
|
||||
// https://ffmpeg.org/ffmpeg-all.html#scale-1
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
mode: 'none',
|
||||
fix: '1280x720',
|
||||
width: '1280',
|
||||
height: '720',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
if (settings.mode === 'height') {
|
||||
mapping.push(`scale=-1:${settings.height}`);
|
||||
} else if (settings.mode === 'width') {
|
||||
mapping.push(`scale=${settings.width}:-1`);
|
||||
} else if (settings.mode === 'fix') {
|
||||
mapping.push(`scale=${settings.fix}`);
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
function Mode(props) {
|
||||
return (
|
||||
<Select label={<Trans>Scale</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="none">
|
||||
<Trans>None</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="fix">
|
||||
<Trans>Fix size</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="height">
|
||||
<Trans>By height</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="width">
|
||||
<Trans>By width</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Mode.defaultProps = {
|
||||
value: '',
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Filter(props) {
|
||||
const settings = init(props.settings);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
if (!newSettings) {
|
||||
newSettings = settings;
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createGraph(newSettings), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
const newSettings = {
|
||||
...settings,
|
||||
[what]: event.target.value,
|
||||
};
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
handleChange(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item xs={settings.mode === 'none' ? 12 : 4}>
|
||||
<Mode allowNone allowCustom label={<Trans>Scale</Trans>} value={settings.mode} onChange={update('mode')}></Mode>
|
||||
</Grid>
|
||||
{settings.mode === 'fix' && (
|
||||
<Grid item xs={8}>
|
||||
<Video.Size allowCustom label={<Trans>Scale size</Trans>} value={settings.fix} onChange={update('fix')}></Video.Size>
|
||||
</Grid>
|
||||
)}
|
||||
{settings.mode === 'width' && (
|
||||
<Grid item xs={8}>
|
||||
<Video.Width allowCustom label={<Trans>Scale size</Trans>} value={settings.width} onChange={update('width')}></Video.Width>
|
||||
</Grid>
|
||||
)}
|
||||
{settings.mode === 'height' && (
|
||||
<Grid item xs={8}>
|
||||
<Video.Height allowCustom label={<Trans>Scale size</Trans>} value={settings.height} onChange={update('height')}></Video.Height>
|
||||
</Grid>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
const filter = 'scale';
|
||||
const name = 'Scale';
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
if (settings.mode === 'height') {
|
||||
return `${name} (-1:${settings.height})`;
|
||||
} else if (settings.mode === 'width') {
|
||||
return `${name} (${settings.width}:-1)`;
|
||||
} else if (settings.mode === 'fix') {
|
||||
return `${name} (${settings.fix})`;
|
||||
}
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
@ -45,9 +45,15 @@ const useStyles = makeStyles((theme) => ({
|
||||
const Component = React.forwardRef((props, ref) => {
|
||||
const classes = useStyles();
|
||||
|
||||
const paperStyle = {};
|
||||
|
||||
if (props.maxWidth > 0) {
|
||||
paperStyle.maxWidth = props.maxWidth + 'px';
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={props.open} onClose={props.onClose} className="modal" disableScrollLock>
|
||||
<Paper className={classes.modalPaper} elevation={0} ref={ref} tabIndex={-1}>
|
||||
<Paper className={classes.modalPaper} elevation={0} ref={ref} tabIndex={-1} style={paperStyle}>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item xs={12} className={classes.modalHeader}>
|
||||
<Typography variant="button">{props.title}</Typography>
|
||||
@ -86,4 +92,5 @@ Component.defaultProps = {
|
||||
onHelp: null,
|
||||
buttonsRight: null,
|
||||
buttonsLefts: null,
|
||||
maxWidth: -1,
|
||||
};
|
||||
|
||||
@ -250,6 +250,7 @@ import * as version from '../version';
|
||||
const defaultMetadata = {
|
||||
version: version.Version,
|
||||
playersite: {},
|
||||
bundle: {},
|
||||
};
|
||||
|
||||
const defaultIngestMetadata = {
|
||||
@ -278,7 +279,7 @@ const defaultIngestMetadata = {
|
||||
reconnect: true,
|
||||
delay: 15,
|
||||
staleTimeout: 30,
|
||||
low_delay: true,
|
||||
low_delay: false,
|
||||
},
|
||||
snapshot: {
|
||||
enable: true,
|
||||
@ -359,6 +360,11 @@ const mergeMetadata = (metadata, base) => {
|
||||
...metadata.playersite,
|
||||
};
|
||||
|
||||
metadata.bundle = {
|
||||
...base.bundle,
|
||||
...metadata.bundle,
|
||||
};
|
||||
|
||||
metadata = transformMetadata(metadata, defaultMetadata.version, transformers);
|
||||
|
||||
return metadata;
|
||||
|
||||
@ -2,7 +2,7 @@ import { i18n } from '@lingui/core';
|
||||
import { t } from '@lingui/macro';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import Handlebars from 'handlebars';
|
||||
import Handlebars from 'handlebars/dist/cjs/handlebars';
|
||||
import SemverSatisfies from 'semver/functions/satisfies';
|
||||
import SemverGt from 'semver/functions/gt';
|
||||
import SemverGte from 'semver/functions/gte';
|
||||
@ -944,9 +944,13 @@ class Restreamer {
|
||||
}
|
||||
|
||||
// Get system metadata
|
||||
async GetMetadata() {
|
||||
async GetMetadata(defaults = true) {
|
||||
let metadata = await this._getMetadata();
|
||||
|
||||
if (defaults === false) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
return M.initMetadata(metadata);
|
||||
}
|
||||
|
||||
@ -1020,8 +1024,8 @@ class Restreamer {
|
||||
const port = getPort(cfg.host);
|
||||
|
||||
address =
|
||||
`srt://${host}${port}/?mode=caller&transtype=live&streamid=#!:m=request,r=${channelid}` +
|
||||
(cfg.token.length !== 0 ? `,token=${cfg.token}` : '') +
|
||||
`srt://${host}${port}/?mode=caller&transtype=live&streamid=${channelid},mode:request` +
|
||||
(cfg.token.length !== 0 ? `,token:${cfg.token}` : '') +
|
||||
(cfg.passphrase.length !== 0 ? `&passphrase=${cfg.passphrase}` : '');
|
||||
} else if (what === 'snapshot+memfs') {
|
||||
// snapshot+memfs
|
||||
@ -1776,7 +1780,7 @@ class Restreamer {
|
||||
output.address =
|
||||
`[${hls_aac_adtstoasc ? 'bsfs/a=aac_adtstoasc:' : ''}${hls_params}]${hls_segment_playlist}` +
|
||||
(rtmp_enabled ? `|[f=flv]{rtmp,name=${channel.channelid}.stream}` : '') +
|
||||
(srt_enabled ? `|[bsfs/v=dump_extra=freq=keyframe:f=mpegts]{srt,name=${channel.channelid},mode=publish}` : '');
|
||||
(srt_enabled ? `|[f=mpegts]{srt,name=${channel.channelid},mode=publish}` : '');
|
||||
} else {
|
||||
// ['-f', 'hls', '-start_number', '0', ...]
|
||||
// adding the '-' in front of the first option, then flatten everything
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { name, version, bundle } from '../package.json';
|
||||
import pkg from '../package.json';
|
||||
|
||||
const Core = '^16.10.1';
|
||||
const FFmpeg = '^4.1.0 || ^5.0.0';
|
||||
const UI = bundle ? bundle : name + ' v' + version;
|
||||
const Version = version;
|
||||
const UI = pkg.bundle ? pkg.bundle : pkg.name + ' v' + pkg.version;
|
||||
const Version = pkg.version;
|
||||
|
||||
export { Core, FFmpeg, UI, Version };
|
||||
|
||||
@ -3,17 +3,24 @@ import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export default function ChannelSelector(props) {
|
||||
const navigate = useNavigate();
|
||||
const [$channelid, setChannelid] = React.useState('');
|
||||
|
||||
React.useEffect(() => {
|
||||
onMount();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onMount = () => {
|
||||
const channelid = props.restreamer.GetCurrentChannelID();
|
||||
React.useEffect(() => {
|
||||
navigate(`/${$channelid}`, { replace: true });
|
||||
}, [navigate, $channelid]);
|
||||
|
||||
navigate(`/${channelid}`, { replace: true });
|
||||
const onMount = () => {
|
||||
setChannelid(props.channelid);
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
ChannelSelector.defaultProps = {
|
||||
channelid: '',
|
||||
};
|
||||
|
||||