Merge branch 'dev'

This commit is contained in:
Ingo Oppermann 2022-11-11 15:18:46 +01:00
commit 8a45e4c41c
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
121 changed files with 13117 additions and 13891 deletions

View File

@ -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

View File

@ -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))

View File

@ -1,4 +1,4 @@
http://127.0.0.1:3000
:3000
encode zstd gzip
file_server {

View File

@ -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" ]

View File

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

View File

@ -1,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": {}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

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

View File

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

View File

@ -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 {

View File

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

View File

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

View File

@ -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>
);

View File

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

View File

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import '@fontsource/dosis';
@ -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>
);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

View File

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

View File

@ -1,10 +1,9 @@
import React from 'react';
import Clappr from './clappr';
import VideoJS from './videojs';
export default function Player(props) {
const type = props.type ? props.type : 'clappr';
const type = props.type ? props.type : 'videojs-internal';
if (type === 'videojs-internal' || type === 'videojs-public') {
const config = {
@ -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} />;
}
}

View File

@ -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 {

View File

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

View File

@ -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>

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -41,6 +41,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -70,6 +70,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -70,6 +70,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -40,6 +40,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -66,6 +66,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -66,6 +66,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -67,6 +67,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -36,6 +36,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -133,6 +133,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -66,6 +66,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -35,6 +35,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -218,6 +218,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -96,6 +96,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

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

View File

@ -149,6 +149,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -125,6 +125,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -149,6 +149,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -36,6 +36,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

@ -35,6 +35,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

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

View File

@ -149,6 +149,7 @@ function Coder(props) {
Coder.defaultProps = {
stream: {},
settings: {},
skills: {},
onChange: function (settings, mapping) {},
};

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -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

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -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

View File

@ -34,7 +34,7 @@ function createGraph(settings) {
}
if (layout !== 'inherit') {
mapping.push(`ocl=${layout}`);
mapping.push(`ochl=${layout}`);
}
if (mapping.length === 0) {

View File

@ -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);

View 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 };

View File

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

View File

@ -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;

View File

@ -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

View File

@ -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 };

View File

@ -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: '',
};

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