Merge branch 'dev'
This commit is contained in:
commit
8b402acbbe
1
.github/workflows/build_restreamer-ui.yaml
vendored
1
.github/workflows/build_restreamer-ui.yaml
vendored
@ -53,6 +53,7 @@ jobs:
|
||||
build-args: |
|
||||
PUBLIC_URL=./
|
||||
NODE_IMAGE=${{ env.NODE_IMAGE }}
|
||||
CADDY_IMAGE=${{ env.CADDY_IMAGE }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
tags: |
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
# RESTREAMER UI
|
||||
RELEASE=1.5.0
|
||||
NODE_IMAGE=node:19.0-alpine3.16
|
||||
RELEASE=1.6.0
|
||||
NODE_IMAGE=node:19.1-alpine3.16
|
||||
CADDY_IMAGE=caddy:2.6.2-alpine
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,8 +1,16 @@
|
||||
# Restreamer-UI
|
||||
|
||||
## v1.5.1 > v1.6.0
|
||||
|
||||
- Add Bob Weaver Deinterlacing Filter ([#465](https://github.com/datarhei/restreamer/issues/465))
|
||||
- Add tests for wizard, network source, and coders
|
||||
- Add Korean translation (thanks to Jihaeng)
|
||||
- Mod splitting wizard in components
|
||||
- Fix wrong call to encoder defaults ([#467](https://github.com/datarhei/restreamer/issues/467))
|
||||
|
||||
## v1.5.0 > v1.5.1
|
||||
|
||||
- Fix FFmpeg version check for RTSP sources (datarhei/restreamer#455)
|
||||
- Fix FFmpeg version check for RTSP sources ([#455](https://github.com/datarhei/restreamer/issues/455))
|
||||
- Fix requires Core >= v16.11.0 and FFmpeg >= 5.1.0
|
||||
|
||||
## v1.4.0 > v1.5.0
|
||||
|
||||
30
package.json
30
package.json
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "restreamer-ui",
|
||||
"version": "1.5.1",
|
||||
"bundle": "restreamer-v2.4.1",
|
||||
"version": "1.6.0",
|
||||
"bundle": "restreamer-v2.4.2",
|
||||
"private": false,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@ -12,27 +12,27 @@
|
||||
"@emotion/styled": "^11.10.5",
|
||||
"@fontsource/dosis": "^4.5.10",
|
||||
"@fontsource/roboto": "^4.5.8",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-brands-svg-icons": "^6.2.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.2.1",
|
||||
"@fortawesome/react-fontawesome": "^0.2.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",
|
||||
"@mui/icons-material": "^5.10.15",
|
||||
"@mui/lab": "^5.0.0-alpha.109",
|
||||
"@mui/material": "^5.10.15",
|
||||
"@mui/styles": "^5.10.15",
|
||||
"@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": "^8.27.0",
|
||||
"eslint": "^8.28.0",
|
||||
"handlebars": "^4.7.7",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"make-plural": "^7.1.0",
|
||||
"make-plural": "^7.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-device-detect": "^2.2.2",
|
||||
@ -41,7 +41,7 @@
|
||||
"react-router-dom": "^6.4.3",
|
||||
"react-scripts": "5.0.1",
|
||||
"semver": "^7.3.8",
|
||||
"typescript": "^4.8.4",
|
||||
"typescript": "^4.9.3",
|
||||
"url-parse": "^1.5.10",
|
||||
"uuid": "^9.0.0",
|
||||
"video.js": "^7.20.3",
|
||||
@ -51,6 +51,8 @@
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts --optimize-for-size build",
|
||||
"test": "react-scripts test",
|
||||
"test-ci": "react-scripts test --watchAll=false --testTimeout 50000",
|
||||
"test-coverage": "react-scripts test --watchAll=false --testTimeout 50000 --coverage",
|
||||
"eject": "react-scripts eject",
|
||||
"i18n-extract": "lingui extract",
|
||||
"i18n-extract:clean": "lingui extract --clean",
|
||||
@ -85,8 +87,8 @@
|
||||
"@lingui/cli": "^3.15.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier": "^2.8.0",
|
||||
"react-error-overlay": "^6.0.11"
|
||||
},
|
||||
"resolutions": {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ import { messages as IT } from './locales/it/messages.js';
|
||||
import { messages as PL } from './locales/pl/messages.js';
|
||||
import { messages as PT } from './locales/pt/messages.js';
|
||||
import { messages as RU } from './locales/ru/messages.js';
|
||||
import { messages as KO } from './locales/ko/messages.js';
|
||||
import * as Storage from './utils/storage';
|
||||
|
||||
i18n.loadLocaleData('en', { plurals: plurals.en });
|
||||
@ -22,6 +23,7 @@ i18n.loadLocaleData('it', { plurals: plurals.it });
|
||||
i18n.loadLocaleData('pl', { plurals: plurals.pl });
|
||||
i18n.loadLocaleData('pt', { plurals: plurals.pt });
|
||||
i18n.loadLocaleData('ru', { plurals: plurals.ru });
|
||||
i18n.loadLocaleData('ko', { plurals: plurals.ko });
|
||||
i18n.load({
|
||||
en: EN,
|
||||
de: DE,
|
||||
@ -31,6 +33,7 @@ i18n.load({
|
||||
pl: PL,
|
||||
pt: PT,
|
||||
ru: RU,
|
||||
ko: KO,
|
||||
});
|
||||
|
||||
const getLanguage = (defaultLanguage, supportedLanguages) => {
|
||||
@ -59,7 +62,7 @@ const getBrowserLanguage = (defaultLanguage) => {
|
||||
return match[0].toLowerCase();
|
||||
};
|
||||
|
||||
i18n.activate(getLanguage('en', ['en', 'de', 'es', 'fr', 'it', 'pl', 'pt', 'ru']));
|
||||
i18n.activate(getLanguage('en', ['en', 'de', 'es', 'fr', 'it', 'pl', 'pt', 'ru', 'ko']));
|
||||
|
||||
export default function Provider(props) {
|
||||
return <I18nProvider i18n={i18n}>{props.children}</I18nProvider>;
|
||||
|
||||
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
1
src/locales/ko/messages.js
Normal file
1
src/locales/ko/messages.js
Normal file
File diff suppressed because one or more lines are too long
2933
src/locales/ko/messages.po
Normal file
2933
src/locales/ko/messages.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ export default function EncodingSelect(props) {
|
||||
|
||||
const handleDecoderChange = (event) => {
|
||||
const decoder = profile.decoder;
|
||||
const stream = props.streams[profile.stream];
|
||||
decoder.coder = event.target.value;
|
||||
|
||||
// If the coder changes, use the coder's default settings
|
||||
@ -39,7 +40,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults(props.skills);
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
decoder.settings = defaults.settings;
|
||||
decoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -58,6 +59,7 @@ export default function EncodingSelect(props) {
|
||||
|
||||
const handleEncoderChange = (event) => {
|
||||
const encoder = profile.encoder;
|
||||
const stream = props.streams[profile.stream];
|
||||
encoder.coder = event.target.value;
|
||||
|
||||
// If the coder changes, use the coder's default settings
|
||||
@ -69,7 +71,7 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
|
||||
if (c !== null) {
|
||||
const defaults = c.defaults(props.skills);
|
||||
const defaults = c.defaults(stream, props.skills);
|
||||
encoder.settings = defaults.settings;
|
||||
encoder.mapping = defaults.mapping;
|
||||
}
|
||||
@ -198,6 +200,8 @@ export default function EncodingSelect(props) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in case there's no decoder for a codec it should be mentioned.
|
||||
|
||||
return (
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@ -42,14 +42,15 @@ export default function LanguageSelect(props) {
|
||||
|
||||
return (
|
||||
<Select className={classes.root} variant="standard" displayEmpty value={i18n.locale} onChange={handleChange}>
|
||||
<MenuItem value="en">English </MenuItem>
|
||||
<MenuItem value="de">Deutsch </MenuItem>
|
||||
<MenuItem value="es">Español </MenuItem>
|
||||
<MenuItem value="fr">Français </MenuItem>
|
||||
<MenuItem value="it">Italiano </MenuItem>
|
||||
<MenuItem value="en">English</MenuItem>
|
||||
<MenuItem value="de">Deutsch</MenuItem>
|
||||
<MenuItem value="es">Español</MenuItem>
|
||||
<MenuItem value="fr">Français</MenuItem>
|
||||
<MenuItem value="it">Italiano</MenuItem>
|
||||
<MenuItem value="pl">Polski</MenuItem>
|
||||
<MenuItem value="pt">Português </MenuItem>
|
||||
<MenuItem value="ru">Русский </MenuItem>
|
||||
<MenuItem value="pt">Português</MenuItem>
|
||||
<MenuItem value="ru">Русский</MenuItem>
|
||||
<MenuItem value="ko">한국어</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: [],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = [];
|
||||
const type = 'audio';
|
||||
const hwaccel = false;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: [],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = [];
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'h264_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -53,12 +60,12 @@ const codecs = ['h264'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'h264_mmal'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['h264'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'hevc_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['hevc'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mjpeg_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mjpeg'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mpeg1_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mpeg1'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mpeg2_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mpeg2'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mpeg2_mmal'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mpeg2'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mpeg4_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mpeg4'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'mpeg4_mmal'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['mpeg4'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -53,12 +60,12 @@ const codecs = ['h264', 'hevc', 'mpeg1', 'mpeg2', 'mpeg4', 'vp8', 'vp9', 'vc1'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'vc1_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['vc1'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'vc1_mmal'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['vc1'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'vp8_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['vp8'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-c:v', 'vp9_cuvid'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['vp9'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
...initialState,
|
||||
@ -8,7 +10,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const mapping = {
|
||||
global: [],
|
||||
local: ['-hwaccel', 'videotoolbox'],
|
||||
@ -19,6 +24,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -27,7 +34,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -51,12 +58,12 @@ const codecs = ['h264'];
|
||||
const type = 'video';
|
||||
const hwaccel = true;
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
if (stream.codec === 'aac') {
|
||||
@ -30,7 +34,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -39,7 +44,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -84,12 +89,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
if (stream.codec === 'aac') {
|
||||
@ -30,7 +34,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -39,7 +44,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -84,12 +89,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
import Helper from '../../helper';
|
||||
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'copy'];
|
||||
|
||||
//if (stream.codec === 'aac') {
|
||||
@ -17,7 +22,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = {};
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -26,7 +32,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -54,12 +60,12 @@ function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = {};
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
@ -26,7 +30,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -35,7 +40,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -80,12 +85,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
@ -26,7 +30,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -35,7 +40,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -80,12 +85,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
// '-qscale:a', '6'
|
||||
const local = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest'];
|
||||
|
||||
@ -27,7 +31,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -36,7 +41,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -81,12 +86,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
import Helper from '../../helper';
|
||||
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-an'];
|
||||
|
||||
const mapping = {
|
||||
@ -13,7 +18,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = {};
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -22,7 +28,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -50,12 +56,12 @@ function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = {};
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans, t } from '@lingui/macro';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import SelectCustom from '../../../../misc/SelectCustom';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -19,7 +20,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
let sampling = settings.sampling;
|
||||
let layout = settings.layout;
|
||||
|
||||
@ -90,7 +94,8 @@ Delay.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -99,7 +104,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -147,12 +152,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Audio from '../../settings/Audio';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -13,7 +14,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest'];
|
||||
|
||||
const mapping = {
|
||||
@ -26,7 +30,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -35,7 +40,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -80,12 +85,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function createMapping(settings) {
|
||||
import Helper from '../../helper';
|
||||
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:v', 'copy'];
|
||||
|
||||
const mapping = {
|
||||
@ -13,6 +18,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = {};
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -21,7 +28,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -43,10 +50,10 @@ function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
return {
|
||||
settings: {},
|
||||
mapping: createMapping({}),
|
||||
mapping: createMapping({}, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -23,7 +24,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'h264_nvenc',
|
||||
@ -163,6 +167,8 @@ RateControl.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -171,7 +177,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -232,12 +238,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import React from 'react';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -16,7 +17,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'h264_omx',
|
||||
@ -50,6 +54,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -58,7 +64,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -110,12 +116,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import { Trans } from '@lingui/macro';
|
||||
import BoxText from '../../../BoxText';
|
||||
import TextField from '../../../TextField';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -75,7 +76,10 @@ Codec Controls
|
||||
4: High
|
||||
*/
|
||||
|
||||
function createMapping(settings, skills) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
@ -131,8 +135,11 @@ function createMapping(settings, skills) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
@ -143,7 +150,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -216,12 +223,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults(skills) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, skills),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -23,7 +24,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const global = [];
|
||||
const local = [];
|
||||
|
||||
@ -97,6 +101,8 @@ Profile.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -105,7 +111,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -163,12 +169,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -21,7 +22,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = [
|
||||
'-codec:v',
|
||||
'h264_videotoolbox',
|
||||
@ -76,6 +80,8 @@ Entropy.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -84,7 +90,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -139,12 +145,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -23,7 +24,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const global = [];
|
||||
const local = [];
|
||||
|
||||
@ -97,6 +101,8 @@ Profile.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -105,7 +111,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -163,12 +169,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function createMapping(settings, stream) {
|
||||
import Helper from '../../helper';
|
||||
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-vn'];
|
||||
|
||||
const mapping = {
|
||||
@ -13,7 +18,8 @@ function createMapping(settings, stream) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = {};
|
||||
const stream = props.stream;
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -22,7 +28,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -50,12 +56,12 @@ function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults(stream) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = {};
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, stream),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
function createMapping(settings) {
|
||||
import Helper from '../../helper';
|
||||
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const local = ['-codec:v', 'rawvideo'];
|
||||
|
||||
const mapping = {
|
||||
@ -13,6 +18,8 @@ function createMapping(settings) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = {};
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -21,7 +28,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
@ -43,10 +50,10 @@ function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
return {
|
||||
settings: {},
|
||||
mapping: createMapping({}),
|
||||
mapping: createMapping({}, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@ import SemverSatisfies from 'semver/functions/satisfies';
|
||||
import Grid from '@mui/material/Grid';
|
||||
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -17,7 +18,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, skills) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
@ -63,8 +67,11 @@ function createMapping(settings, skills) {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
@ -75,7 +82,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -129,12 +136,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults(skills) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, skills),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -23,7 +24,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
const global = [];
|
||||
const local = [];
|
||||
|
||||
@ -97,6 +101,8 @@ Profile.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
const handleChange = (newSettings) => {
|
||||
let automatic = false;
|
||||
@ -105,7 +111,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -163,12 +169,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -24,7 +25,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, skills) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
@ -120,8 +124,11 @@ Tune.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
@ -132,7 +139,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -195,12 +202,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults(skills) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, skills),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import { Trans } from '@lingui/macro';
|
||||
|
||||
import Select from '../../../Select';
|
||||
import Video from '../../settings/Video';
|
||||
import Helper from '../../helper';
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
@ -24,7 +25,10 @@ function init(initialState) {
|
||||
return state;
|
||||
}
|
||||
|
||||
function createMapping(settings, skills) {
|
||||
function createMapping(settings, stream, skills) {
|
||||
stream = Helper.InitStream(stream);
|
||||
skills = Helper.InitSkills(skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
@ -120,8 +124,11 @@ Tune.defaultProps = {
|
||||
|
||||
function Coder(props) {
|
||||
const settings = init(props.settings);
|
||||
const stream = Helper.InitStream(props.stream);
|
||||
const skills = Helper.InitSkills(props.skills);
|
||||
|
||||
let ffversion = 4;
|
||||
if (SemverSatisfies(props.skills.ffmpeg.version, '^5.0.0')) {
|
||||
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
|
||||
ffversion = 5;
|
||||
}
|
||||
|
||||
@ -132,7 +139,7 @@ function Coder(props) {
|
||||
automatic = true;
|
||||
}
|
||||
|
||||
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
|
||||
props.onChange(newSettings, createMapping(newSettings, stream, skills), automatic);
|
||||
};
|
||||
|
||||
const update = (what) => (event) => {
|
||||
@ -195,12 +202,12 @@ function summarize(settings) {
|
||||
return `${name}, ${settings.bitrate} kbit/s, ${settings.fps} FPS, Preset: ${settings.preset}, Profile: ${settings.profile}`;
|
||||
}
|
||||
|
||||
function defaults(skills) {
|
||||
function defaults(stream, skills) {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
mapping: createMapping(settings, skills),
|
||||
mapping: createMapping(settings, stream, skills),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ export { coder, name, codecs, type, hwaccel, defaults, Coder as component };
|
||||
|
||||
### defaults
|
||||
|
||||
The `defaults()` function returns the default settings and mappings for this decoder. It is an object of this shape:
|
||||
The `defaults(stream, skills)` function returns the default settings and mappings for this decoder. It is an object of this shape:
|
||||
|
||||
```
|
||||
{
|
||||
@ -80,7 +80,7 @@ export { coder, name, codec, type, hwaccel, summarize, defaults, Coder as compon
|
||||
|
||||
### defaults
|
||||
|
||||
The `defaults()` function returns the default settings and mappings for this encoder. It is an object of this shape:
|
||||
The `defaults(stream, skills)` function returns the default settings and mappings for this encoder. It is an object of this shape:
|
||||
|
||||
```
|
||||
{
|
||||
|
||||
35
src/misc/coders/coders.test.js
Normal file
35
src/misc/coders/coders.test.js
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { render, act } from '../../utils/testing';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import * as Decoders from './Decoders';
|
||||
import * as Encoders from './Encoders';
|
||||
|
||||
const audiodecoders = Decoders.Audio.List();
|
||||
const videodecoders = Decoders.Video.List();
|
||||
const audioencoders = Encoders.Audio.List();
|
||||
const videoencoders = Encoders.Video.List();
|
||||
|
||||
const testfunc = async (coder) => {
|
||||
const defaults = coder.defaults();
|
||||
|
||||
let $settings = {};
|
||||
let $mapping = {};
|
||||
|
||||
const handleChange = (settings, mapping) => {
|
||||
$settings = settings;
|
||||
$mapping = mapping;
|
||||
};
|
||||
|
||||
await act(async () => {
|
||||
render(<coder.component onChange={handleChange} />);
|
||||
});
|
||||
|
||||
expect($settings).toStrictEqual(defaults.settings);
|
||||
expect($mapping).toStrictEqual(defaults.mapping);
|
||||
};
|
||||
|
||||
test.each(audiodecoders)('coder:decoder:$type $coder', testfunc);
|
||||
test.each(videodecoders)('coder:decoder:$type $coder', testfunc);
|
||||
test.each(audioencoders)('coder:encoder:$type $coder', testfunc);
|
||||
test.each(videoencoders)('coder:encoder:$type $coder', testfunc);
|
||||
35
src/misc/coders/helper/index.js
Normal file
35
src/misc/coders/helper/index.js
Normal file
@ -0,0 +1,35 @@
|
||||
function InitStream(initialStream) {
|
||||
if (!initialStream) {
|
||||
initialStream = {};
|
||||
}
|
||||
|
||||
let stream = {
|
||||
codec: '',
|
||||
...initialStream,
|
||||
};
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
function InitSkills(initialSkills) {
|
||||
if (!initialSkills) {
|
||||
initialSkills = {};
|
||||
}
|
||||
|
||||
let skills = {
|
||||
ffmpeg: {},
|
||||
...initialSkills,
|
||||
};
|
||||
|
||||
skills.ffmpeg = {
|
||||
version: '5.0.0',
|
||||
...skills.ffmpeg,
|
||||
};
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
export default {
|
||||
InitStream,
|
||||
InitSkills,
|
||||
};
|
||||
@ -9,6 +9,7 @@ import * as Scale from './video/Scale';
|
||||
import * as Transpose from './video/Transpose';
|
||||
import * as HFlip from './video/HFlip';
|
||||
import * as VFlip from './video/VFlip';
|
||||
import * as Bwdif from './video/Bwdif';
|
||||
|
||||
// Register filters type: audio/video
|
||||
class Registry {
|
||||
@ -56,6 +57,7 @@ videoRegistry.Register(Scale);
|
||||
videoRegistry.Register(Transpose);
|
||||
videoRegistry.Register(HFlip);
|
||||
videoRegistry.Register(VFlip);
|
||||
videoRegistry.Register(Bwdif);
|
||||
|
||||
// Export registrys for ../SelectFilters.js
|
||||
export { audioRegistry as Audio, videoRegistry as Video };
|
||||
|
||||
170
src/misc/filters/video/Bwdif.js
Normal file
170
src/misc/filters/video/Bwdif.js
Normal file
@ -0,0 +1,170 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
|
||||
import Checkbox from '../../Checkbox';
|
||||
import Select from '../../Select';
|
||||
|
||||
// Deinterlace the input video ("bwdif" stands for "Bob Weaver Deinterlacing Filter").
|
||||
// http://ffmpeg.org/ffmpeg-all.html#bwdif
|
||||
|
||||
function init(initialState) {
|
||||
const state = {
|
||||
enabled: false,
|
||||
mode: '1',
|
||||
parity: '-1',
|
||||
deint: '0',
|
||||
...initialState,
|
||||
};
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function createGraph(settings) {
|
||||
settings = init(settings);
|
||||
|
||||
const mapping = [];
|
||||
|
||||
if (settings.enabled) {
|
||||
mapping.push(`bwdif=mode=${settings.mode}:parity=${settings.parity}:deint=${settings.deint}`);
|
||||
}
|
||||
|
||||
return mapping.join(',');
|
||||
}
|
||||
|
||||
function Mode(props) {
|
||||
return (
|
||||
<Select label={<Trans>Deinterlace mode</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="0">
|
||||
<Trans>Each frames</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="1">
|
||||
<Trans>Each field</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Mode.defaultProps = {
|
||||
value: '',
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Parity(props) {
|
||||
return (
|
||||
<Select label={<Trans>Deinterlace parity</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="0">
|
||||
<Trans>Top field</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="1">
|
||||
<Trans>Bottom field</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="-1">
|
||||
<Trans>Auto</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Parity.defaultProps = {
|
||||
value: '',
|
||||
onChange: function (event) {},
|
||||
};
|
||||
|
||||
function Deint(props) {
|
||||
return (
|
||||
<Select label={<Trans>Deinterlace deint</Trans>} value={props.value} onChange={props.onChange}>
|
||||
<MenuItem value="0">
|
||||
<Trans>All frames</Trans>
|
||||
</MenuItem>
|
||||
<MenuItem value="1">
|
||||
<Trans>Marked frames</Trans>
|
||||
</MenuItem>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
|
||||
Deint.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,
|
||||
};
|
||||
if (['enabled'].includes(what)) {
|
||||
newSettings[what] = !settings.enabled;
|
||||
} else {
|
||||
newSettings[what] = event.target.value;
|
||||
}
|
||||
|
||||
handleChange(newSettings);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
handleChange(null);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Grid item>
|
||||
<Checkbox label={<Trans>Deinterlace (bwdif)</Trans>} checked={settings.enabled} onChange={update('enabled')} />
|
||||
</Grid>
|
||||
{settings.enabled && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Mode value={settings.mode} onChange={update('mode')}></Mode>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Parity value={settings.parity} onChange={update('parity')}></Parity>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Deint value={settings.deint} onChange={update('deint')}></Deint>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
Filter.defaultProps = {
|
||||
settings: {},
|
||||
onChange: function (settings, mapping) {},
|
||||
};
|
||||
|
||||
const filter = 'bwdif';
|
||||
const name = 'Deinterlacing Filter';
|
||||
const type = 'video';
|
||||
const hwaccel = false;
|
||||
|
||||
function summarize(settings) {
|
||||
return `${name}`;
|
||||
}
|
||||
|
||||
function defaults() {
|
||||
const settings = init({});
|
||||
|
||||
return {
|
||||
settings: settings,
|
||||
graph: createGraph(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export { name, filter, type, hwaccel, summarize, defaults, createGraph, Filter as component };
|
||||
@ -92,7 +92,7 @@ function Filter(props) {
|
||||
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>
|
||||
<Mode value={settings.mode} onChange={update('mode')}></Mode>
|
||||
</Grid>
|
||||
{settings.mode === 'fix' && (
|
||||
<Grid item xs={8}>
|
||||
|
||||
@ -17,11 +17,11 @@ const topics = {
|
||||
en: 'https://docs.datarhei.com/restreamer/knowledge-base/manual/edit-livestream/license',
|
||||
de: 'https://docs.datarhei.com/restreamer/v/de/wissensdatenbank/user-guides/streameinstellungen/lizenz',
|
||||
},
|
||||
'login': {
|
||||
login: {
|
||||
en: 'https://docs.datarhei.com/restreamer/knowledge-base/manual/login',
|
||||
de: 'https://docs.datarhei.com/restreamer/v/de/wissensdatenbank/user-guides/dashboard',
|
||||
},
|
||||
'main': {
|
||||
main: {
|
||||
en: 'https://docs.datarhei.com/restreamer/knowledge-base/manual/main-screen',
|
||||
de: 'https://docs.datarhei.com/restreamer/v/de/wissensdatenbank/user-guides/hauptbildschirm',
|
||||
},
|
||||
@ -73,7 +73,7 @@ const topics = {
|
||||
en: 'https://docs.datarhei.com/restreamer/knowledge-base/manual/process-report',
|
||||
de: 'https://docs.datarhei.com/restreamer/v/de/wissensdatenbank/user-guides/prozess-details',
|
||||
},
|
||||
'publication': {
|
||||
publication: {
|
||||
en: 'https://docs.datarhei.com/restreamer/knowledge-base/manual/publications',
|
||||
de: 'https://docs.datarhei.com/restreamer/v/de/wissensdatenbank/user-guides/publication-services',
|
||||
},
|
||||
|
||||
92
src/utils/testing.js
Normal file
92
src/utils/testing.js
Normal file
@ -0,0 +1,92 @@
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom';
|
||||
import theme from '../theme';
|
||||
import I18n from '../I18n';
|
||||
|
||||
/*
|
||||
This is wrapper for the render method in order to provide all required providers and
|
||||
not to repeat them for each test.
|
||||
|
||||
If you want to debug a test by having a look at the current output of the component,
|
||||
simply import the "screen" object from this file and use the "debug()" method:
|
||||
|
||||
test('displays the header and paragraph text', () => {
|
||||
render(<Travel />)
|
||||
screen.debug()
|
||||
})
|
||||
|
||||
or of a specific element in the component:
|
||||
|
||||
test('displays the header and paragraph text', () => {
|
||||
render(<Travel />)
|
||||
const header = screen.getByRole('heading', { name: /travel anywhere/i })
|
||||
screen.debug(header)
|
||||
})
|
||||
|
||||
More about "render" and "screen" on https://testing-library.com/docs
|
||||
|
||||
For testing certain conditions, check out https://jestjs.io/docs/expect and for
|
||||
running tests, check out https://jestjs.io/docs/api.
|
||||
|
||||
As an alternative to fireEvent there is the userEvent which may simulate better
|
||||
a user clicking around. Example:
|
||||
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
test("radio", () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<form>
|
||||
<label>
|
||||
First <input type="radio" name="radio1" value="first" />
|
||||
</label>
|
||||
<label>
|
||||
Second <input type="radio" name="radio1" value="second" />
|
||||
</label>
|
||||
</form>
|
||||
)
|
||||
|
||||
await user.click(screen.getByLabelText("Second"));
|
||||
});
|
||||
*/
|
||||
|
||||
const NoRoute = (props) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
const AllTheProviders =
|
||||
(initialEntries, path) =>
|
||||
({ children }) => {
|
||||
if (typeof initialEntries === 'undefined') {
|
||||
initialEntries = '/';
|
||||
}
|
||||
if (typeof path === 'undefined') {
|
||||
path = '/';
|
||||
}
|
||||
return (
|
||||
<StyledEngineProvider injectFirst>
|
||||
<ThemeProvider theme={theme}>
|
||||
<I18n>
|
||||
<MemoryRouter initialEntries={[initialEntries]}>
|
||||
<Routes>
|
||||
<Route path={path} element={children} />
|
||||
<Route path="*" element={<NoRoute />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</I18n>
|
||||
</ThemeProvider>
|
||||
</StyledEngineProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const customRender = (ui, options, initialEntries, path) => render(ui, { wrapper: AllTheProviders(initialEntries, path), ...options });
|
||||
|
||||
// re-export everything
|
||||
export * from '@testing-library/react';
|
||||
|
||||
// override render method
|
||||
export { customRender as render };
|
||||
@ -108,7 +108,7 @@ const initConfig = (initialConfig) => {
|
||||
local: 'localhost',
|
||||
token: '',
|
||||
passphrase: '',
|
||||
name: '',
|
||||
name: 'external',
|
||||
...config.srt,
|
||||
};
|
||||
|
||||
@ -152,7 +152,9 @@ const initSkills = (initialSkills) => {
|
||||
};
|
||||
|
||||
if (skills.formats.demuxers.includes('rtsp')) {
|
||||
skills.protocols.input.push('rtsp');
|
||||
if (!skills.protocols.input.includes('rtsp')) {
|
||||
skills.protocols.input.push('rtsp');
|
||||
}
|
||||
}
|
||||
|
||||
return skills;
|
||||
@ -181,8 +183,10 @@ const createInputs = (settings, config, skills) => {
|
||||
if (settings.mode === 'push') {
|
||||
if (settings.push.type === 'hls') {
|
||||
input.address = getLocalHLS(config);
|
||||
input.options.push('-analyzeduration', '20000000');
|
||||
} else if (settings.push.type === 'rtmp') {
|
||||
input.address = getLocalRTMP(config);
|
||||
input.options.push('-analyzeduration', '3000000');
|
||||
} else if (settings.push.type === 'srt') {
|
||||
input.address = getLocalSRT(config);
|
||||
} else {
|
||||
@ -315,14 +319,14 @@ const isAuthProtocol = (url) => {
|
||||
const isSupportedProtocol = (url, supportedProtocols) => {
|
||||
const protocol = getProtocol(url);
|
||||
if (protocol.length === 0) {
|
||||
return 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!supportedProtocols.includes(protocol)) {
|
||||
return -1;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 1;
|
||||
return true;
|
||||
};
|
||||
|
||||
const getHLSAddress = (host, credentials, name, secure) => {
|
||||
@ -406,6 +410,7 @@ function Pull(props) {
|
||||
const settings = props.settings;
|
||||
const protocolClass = getProtocolClass(settings.address);
|
||||
const authProtocol = isAuthProtocol(settings.address);
|
||||
const validURL = isValidURL(settings.address);
|
||||
const supportedProtocol = isSupportedProtocol(settings.address, props.skills.protocols.input);
|
||||
|
||||
return (
|
||||
@ -428,69 +433,133 @@ function Pull(props) {
|
||||
<Trans>Supports HTTP (HLS, DASH), RTP, RTSP, RTMP, SRT and more.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{supportedProtocol === -1 && (
|
||||
<Grid item xs={12} align="center">
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography>
|
||||
<Trans>This protocol is unknown or not supported by the available FFmpeg binary.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
</Grid>
|
||||
)}
|
||||
{supportedProtocol === 1 && (
|
||||
{validURL === true && (
|
||||
<React.Fragment>
|
||||
{authProtocol && (
|
||||
<React.Fragment>
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label={<Trans>Username</Trans>}
|
||||
value={settings.username}
|
||||
onChange={props.onChange('', 'username')}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>Username for the device.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Password
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label={<Trans>Password</Trans>}
|
||||
value={settings.password}
|
||||
onChange={props.onChange('', 'password')}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>Password for the device.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Accordion className="accordion">
|
||||
<AccordionSummary elevation={0} expandIcon={<ArrowDropDownIcon />}>
|
||||
{!supportedProtocol ? (
|
||||
<Grid item xs={12} align="center">
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography>
|
||||
<Trans>Advanced settings</Trans>
|
||||
<Trans>This protocol is unknown or not supported by the available FFmpeg binary.</Trans>
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{protocolClass === 'rtsp' && (
|
||||
<React.Fragment>
|
||||
</BoxText>
|
||||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
{authProtocol && (
|
||||
<React.Fragment>
|
||||
<Grid item md={6} xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label={<Trans>Username</Trans>}
|
||||
value={settings.username}
|
||||
onChange={props.onChange('', 'username')}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>Username for the device.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item md={6} xs={12}>
|
||||
<Password
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label={<Trans>Password</Trans>}
|
||||
value={settings.password}
|
||||
onChange={props.onChange('', 'password')}
|
||||
/>
|
||||
<Typography variant="caption">
|
||||
<Trans>Password for the device.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Accordion className="accordion">
|
||||
<AccordionSummary elevation={0} expandIcon={<ArrowDropDownIcon />}>
|
||||
<Typography>
|
||||
<Trans>Advanced settings</Trans>
|
||||
</Typography>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{protocolClass === 'rtsp' && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
<Trans>RTSP</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>UDP transport</Trans>}
|
||||
checked={settings.rtsp.udp}
|
||||
onChange={props.onChange('rtsp', 'udp')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
fullWidth
|
||||
label={<Trans>Socket timeout (microseconds)</Trans>}
|
||||
value={settings.rtsp.stimeout}
|
||||
onChange={props.onChange('rtsp', 'stimeout')}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{protocolClass === 'http' && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
<Trans>HTTP and HTTPS</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>Read input at native speed</Trans>}
|
||||
checked={settings.http.readNative}
|
||||
onChange={props.onChange('http', 'readNative')}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Force input framerate</Trans>}
|
||||
checked={settings.http.forceFramerate}
|
||||
onChange={props.onChange('http', 'forceFramerate')}
|
||||
/>
|
||||
</Grid>
|
||||
{settings.http.forceFramerate === true && (
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
fullWidth
|
||||
label={<Trans>Framerate</Trans>}
|
||||
value={settings.http.framerate}
|
||||
onChange={props.onChange('http', 'framerate')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="User-Agent"
|
||||
value={settings.http.userAgent}
|
||||
onChange={props.onChange('http', 'userAgent')}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
<Trans>RTSP</Trans>
|
||||
<Trans>General</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>UDP transport</Trans>}
|
||||
checked={settings.rtsp.udp}
|
||||
onChange={props.onChange('rtsp', 'udp')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
@ -498,95 +567,39 @@ function Pull(props) {
|
||||
min="0"
|
||||
step="1"
|
||||
fullWidth
|
||||
label={<Trans>Socket timeout (microseconds)</Trans>}
|
||||
value={settings.rtsp.stimeout}
|
||||
onChange={props.onChange('rtsp', 'stimeout')}
|
||||
label="thread_queue_size"
|
||||
value={settings.general.thread_queue_size}
|
||||
onChange={props.onChange('general', 'thread_queue_size')}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{protocolClass === 'http' && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
<Trans>HTTP and HTTPS</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Checkbox
|
||||
label={<Trans>Read input at native speed</Trans>}
|
||||
checked={settings.http.readNative}
|
||||
onChange={props.onChange('http', 'readNative')}
|
||||
/>
|
||||
<Checkbox
|
||||
label={<Trans>Force input framerate</Trans>}
|
||||
checked={settings.http.forceFramerate}
|
||||
onChange={props.onChange('http', 'forceFramerate')}
|
||||
/>
|
||||
<MultiSelect
|
||||
type="select"
|
||||
label="flags"
|
||||
value={settings.general.fflags}
|
||||
onChange={props.onChange('general', 'fflags')}
|
||||
>
|
||||
<MultiSelectOption value="discardcorrupt" name="discardcorrupt" />
|
||||
<MultiSelectOption value="fastseek" name="fastseek" />
|
||||
<MultiSelectOption value="genpts" name="genpts" />
|
||||
<MultiSelectOption value="igndts" name="igndts" />
|
||||
<MultiSelectOption value="ignidx" name="ignidx" />
|
||||
<MultiSelectOption value="nobuffer" name="nobuffer" />
|
||||
<MultiSelectOption value="nofillin" name="nofillin" />
|
||||
<MultiSelectOption value="noparse" name="noparse" />
|
||||
<MultiSelectOption value="sortdts" name="sortdts" />
|
||||
</MultiSelect>
|
||||
</Grid>
|
||||
{settings.http.forceFramerate === true && (
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
fullWidth
|
||||
label={<Trans>Framerate</Trans>}
|
||||
value={settings.http.framerate}
|
||||
onChange={props.onChange('http', 'framerate')}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
label="User-Agent"
|
||||
value={settings.http.userAgent}
|
||||
onChange={props.onChange('http', 'userAgent')}
|
||||
/>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h3">
|
||||
<Trans>General</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TextField
|
||||
variant="outlined"
|
||||
type="number"
|
||||
min="0"
|
||||
step="1"
|
||||
fullWidth
|
||||
label="thread_queue_size"
|
||||
value={settings.general.thread_queue_size}
|
||||
onChange={props.onChange('general', 'thread_queue_size')}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<MultiSelect type="select" label="flags" value={settings.general.fflags} onChange={props.onChange('general', 'fflags')}>
|
||||
<MultiSelectOption value="discardcorrupt" name="discardcorrupt" />
|
||||
<MultiSelectOption value="fastseek" name="fastseek" />
|
||||
<MultiSelectOption value="genpts" name="genpts" />
|
||||
<MultiSelectOption value="igndts" name="igndts" />
|
||||
<MultiSelectOption value="ignidx" name="ignidx" />
|
||||
<MultiSelectOption value="nobuffer" name="nobuffer" />
|
||||
<MultiSelectOption value="nofillin" name="nofillin" />
|
||||
<MultiSelectOption value="noparse" name="noparse" />
|
||||
<MultiSelectOption value="sortdts" name="sortdts" />
|
||||
</MultiSelect>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<FormInlineButton disabled={!isValidURL(settings.address) || supportedProtocol <= 0} onClick={props.onProbe}>
|
||||
<FormInlineButton disabled={!validURL || !supportedProtocol} onClick={props.onProbe}>
|
||||
<Trans>Probe</Trans>
|
||||
</FormInlineButton>
|
||||
</Grid>
|
||||
@ -602,7 +615,7 @@ function Push(props) {
|
||||
const supportsRTMP = isSupportedProtocol('rtmp://', props.skills.protocols.input);
|
||||
const supportsSRT = isSupportedProtocol('srt://', props.skills.protocols.input);
|
||||
|
||||
if (!supportsRTMP && supportsSRT) {
|
||||
if (!supportsRTMP && !supportsSRT) {
|
||||
return (
|
||||
<Grid container alignItems="flex-start" spacing={2} className={classes.gridContainer}>
|
||||
<Grid item xs={12} align="center">
|
||||
|
||||
330
src/views/Edit/Sources/Network.test.js
Normal file
330
src/views/Edit/Sources/Network.test.js
Normal file
@ -0,0 +1,330 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent } from '../../../utils/testing';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import * as Network from './Network';
|
||||
|
||||
const $skills_ffmpeg5 = {
|
||||
ffmpeg: {
|
||||
version: '5.1.2',
|
||||
},
|
||||
formats: {
|
||||
demuxers: ['rtsp'],
|
||||
},
|
||||
protocols: {
|
||||
input: ['http', 'https', 'rtmp', 'rtmps', 'srt'],
|
||||
},
|
||||
};
|
||||
|
||||
const $skills_ffmpeg4 = {
|
||||
ffmpeg: {
|
||||
version: '4.4.1',
|
||||
},
|
||||
formats: {
|
||||
demuxers: ['rtsp'],
|
||||
},
|
||||
protocols: {
|
||||
input: ['http', 'https', 'rtmp', 'rtmps', 'srt'],
|
||||
},
|
||||
};
|
||||
|
||||
const $config = {
|
||||
rtmp: {
|
||||
enabled: true,
|
||||
app: '/live',
|
||||
token: 'foobar',
|
||||
},
|
||||
srt: {
|
||||
enabled: true,
|
||||
token: 'foobar',
|
||||
passphrase: 'bazfoobazfoo',
|
||||
},
|
||||
};
|
||||
|
||||
test('source:network pull', async () => {
|
||||
let $settings = {
|
||||
mode: 'pull',
|
||||
};
|
||||
const handleChange = (settings) => {
|
||||
$settings = settings;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
let { getByLabelText, queryByText, rerender } = render(<Source onChange={handleChange} />);
|
||||
|
||||
expect(queryByText(`This protocol is unknown or not supported by the available FFmpeg binary.`)).toBe(null);
|
||||
|
||||
const input = getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/stream' } });
|
||||
|
||||
expect($settings.mode).toBe('pull');
|
||||
expect($settings.address).toBe('rtsp://127.0.0.1/live/stream');
|
||||
|
||||
rerender(<Source settings={$settings} onChange={handleChange} />);
|
||||
|
||||
expect(queryByText(`This protocol is unknown or not supported by the available FFmpeg binary.`)).toBeInTheDocument();
|
||||
|
||||
rerender(<Source settings={$settings} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect(queryByText(`This protocol is unknown or not supported by the available FFmpeg binary.`)).toBe(null);
|
||||
});
|
||||
|
||||
const pullmatrix = {
|
||||
settings: {
|
||||
mode: 'pull',
|
||||
address: '',
|
||||
username: 'admin',
|
||||
password: 'foobar',
|
||||
rtsp: {
|
||||
udp: false,
|
||||
stimeout: 5000000,
|
||||
},
|
||||
http: {
|
||||
readNative: true,
|
||||
forceFramerate: true,
|
||||
framerate: 25,
|
||||
userAgent: 'foobaz/1',
|
||||
},
|
||||
general: {
|
||||
fflags: ['genpts'],
|
||||
thread_queue_size: 512,
|
||||
},
|
||||
},
|
||||
tests: [],
|
||||
};
|
||||
|
||||
pullmatrix.tests = [
|
||||
{
|
||||
name: 'RTSP',
|
||||
settings: { ...pullmatrix.settings, address: 'rtsp://127.0.0.1/live/stream' },
|
||||
skills: $skills_ffmpeg4,
|
||||
input: {
|
||||
address: 'rtsp://admin:foobar@127.0.0.1/live/stream',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-stimeout', 5000000, '-rtsp_transport', 'tcp'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'RTMP',
|
||||
settings: { ...pullmatrix.settings, address: 'rtmp://127.0.0.1/live/stream' },
|
||||
skills: $skills_ffmpeg4,
|
||||
input: {
|
||||
address: 'rtmp://admin:foobar@127.0.0.1/live/stream',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '3000000'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'HTTP',
|
||||
settings: { ...pullmatrix.settings, address: 'http://127.0.0.1/live/stream.m3u8' },
|
||||
skills: $skills_ffmpeg4,
|
||||
input: {
|
||||
address: 'http://admin:foobar@127.0.0.1/live/stream.m3u8',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '20000000', '-re', '-r', 25, '-user_agent', 'foobaz/1'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SRT',
|
||||
settings: { ...pullmatrix.settings, address: 'srt://127.0.0.1?mode=caller&streamid=foobar' },
|
||||
skills: $skills_ffmpeg4,
|
||||
input: {
|
||||
address: 'srt://127.0.0.1?mode=caller&streamid=foobar',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'RTSP',
|
||||
settings: { ...pullmatrix.settings, address: 'rtsp://127.0.0.1/live/stream' },
|
||||
skills: $skills_ffmpeg5,
|
||||
input: {
|
||||
address: 'rtsp://admin:foobar@127.0.0.1/live/stream',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-timeout', 5000000, '-rtsp_transport', 'tcp'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'RTMP',
|
||||
settings: { ...pullmatrix.settings, address: 'rtmp://127.0.0.1/live/stream' },
|
||||
skills: $skills_ffmpeg5,
|
||||
input: {
|
||||
address: 'rtmp://admin:foobar@127.0.0.1/live/stream',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '3000000'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'HTTP',
|
||||
settings: { ...pullmatrix.settings, address: 'http://127.0.0.1/live/stream.m3u8' },
|
||||
skills: $skills_ffmpeg5,
|
||||
input: {
|
||||
address: 'http://admin:foobar@127.0.0.1/live/stream.m3u8',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '20000000', '-re', '-r', 25, '-user_agent', 'foobaz/1'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SRT',
|
||||
settings: { ...pullmatrix.settings, address: 'srt://127.0.0.1?mode=caller&streamid=foobar' },
|
||||
skills: $skills_ffmpeg5,
|
||||
input: {
|
||||
address: 'srt://127.0.0.1?mode=caller&streamid=foobar',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(pullmatrix.tests)('source:network pull $name input with ffmpeg $skills.ffmpeg.version', async (data) => {
|
||||
let $inputs = [];
|
||||
const handleProbe = (_, inputs) => {
|
||||
$inputs = inputs;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
|
||||
let { getByText, getByRole } = render(<Source settings={data.settings} skills={data.skills} onProbe={handleProbe} />);
|
||||
|
||||
expect(getByText('Probe')).toBeInTheDocument();
|
||||
|
||||
const button = getByRole('button', { name: 'Probe' });
|
||||
fireEvent.click(button, { bubbles: true });
|
||||
|
||||
expect($inputs.length).toBe(1);
|
||||
expect($inputs[0]).toStrictEqual(data.input);
|
||||
});
|
||||
|
||||
test('source:network push', async () => {
|
||||
let $settings = {
|
||||
mode: 'push',
|
||||
push: {
|
||||
type: 'rtmp',
|
||||
},
|
||||
};
|
||||
const handleChange = (settings) => {
|
||||
$settings = settings;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
let { queryByText, rerender } = render(<Source settings={$settings} onChange={handleChange} />);
|
||||
|
||||
expect($settings.mode).toBe('push');
|
||||
|
||||
expect(queryByText(`The available FFmpeg binary doesn't support any of the required protocols.`)).toBeInTheDocument();
|
||||
|
||||
rerender(<Source settings={$settings} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect(queryByText(`The available FFmpeg binary doesn't support any of the required protocols.`)).toBe(null);
|
||||
});
|
||||
|
||||
test('source:network push RTMP', async () => {
|
||||
let $settings = {
|
||||
mode: 'push',
|
||||
push: {
|
||||
type: 'rtmp',
|
||||
},
|
||||
};
|
||||
const handleChange = (settings) => {
|
||||
$settings = settings;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
let { getByText, queryByText, rerender } = render(<Source settings={$settings} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect($settings.mode).toBe('push');
|
||||
expect($settings.push.type).toBe('rtmp');
|
||||
|
||||
expect(queryByText(`Enable RTMP server ...`)).toBeInTheDocument();
|
||||
|
||||
rerender(<Source settings={$settings} config={$config} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect(getByText('Probe')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('source:network push SRT', async () => {
|
||||
let $settings = {
|
||||
mode: 'push',
|
||||
push: {
|
||||
type: 'srt',
|
||||
},
|
||||
};
|
||||
const handleChange = (settings) => {
|
||||
$settings = settings;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
let { getByText, queryByText, rerender } = render(<Source settings={$settings} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect($settings.mode).toBe('push');
|
||||
expect($settings.push.type).toBe('srt');
|
||||
|
||||
expect(queryByText(`Enable SRT server ...`)).toBeInTheDocument();
|
||||
|
||||
rerender(<Source settings={$settings} config={$config} skills={$skills_ffmpeg5} onChange={handleChange} />);
|
||||
|
||||
expect(getByText('Probe')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
const pushmatrix = {
|
||||
settings: {
|
||||
mode: 'push',
|
||||
push: {
|
||||
type: '',
|
||||
},
|
||||
},
|
||||
tests: [],
|
||||
};
|
||||
|
||||
pushmatrix.tests = [
|
||||
{
|
||||
name: 'RTMP',
|
||||
settings: { ...pushmatrix.settings, push: { ...pushmatrix.push, type: 'rtmp' } },
|
||||
skills: $skills_ffmpeg4,
|
||||
config: $config,
|
||||
input: {
|
||||
address: 'rtmp://localhost/live/external.stream?token=foobar',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '3000000'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SRT',
|
||||
settings: { ...pushmatrix.settings, push: { ...pushmatrix.push, type: 'srt' } },
|
||||
skills: $skills_ffmpeg4,
|
||||
config: $config,
|
||||
input: {
|
||||
address: 'srt://localhost?mode=caller&transtype=live&streamid=external,mode:request,token:foobar&passphrase=bazfoobazfoo',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'RTMP',
|
||||
settings: { ...pushmatrix.settings, push: { ...pushmatrix.push, type: 'rtmp' } },
|
||||
skills: $skills_ffmpeg5,
|
||||
config: $config,
|
||||
input: {
|
||||
address: 'rtmp://localhost/live/external.stream?token=foobar',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512, '-analyzeduration', '3000000'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'SRT',
|
||||
settings: { ...pushmatrix.settings, push: { ...pushmatrix.push, type: 'srt' } },
|
||||
skills: $skills_ffmpeg5,
|
||||
config: $config,
|
||||
input: {
|
||||
address: 'srt://localhost?mode=caller&transtype=live&streamid=external,mode:request,token:foobar&passphrase=bazfoobazfoo',
|
||||
options: ['-fflags', '+genpts', '-thread_queue_size', 512],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
test.each(pushmatrix.tests)('source:network push $name input with ffmpeg $skills.ffmpeg.version', async (data) => {
|
||||
let $inputs = [];
|
||||
const handleProbe = (_, inputs) => {
|
||||
$inputs = inputs;
|
||||
};
|
||||
|
||||
const Source = Network.component;
|
||||
let { getByText, getByRole } = render(<Source settings={data.settings} config={data.config} skills={data.skills} onProbe={handleProbe} />);
|
||||
|
||||
expect(getByText('Probe')).toBeInTheDocument();
|
||||
|
||||
const button = getByRole('button', { name: 'Probe' });
|
||||
fireEvent.click(button, { bubbles: true });
|
||||
|
||||
expect($inputs.length).toBe(1);
|
||||
expect($inputs[0]).toStrictEqual(data.input);
|
||||
});
|
||||
58
src/views/Edit/Wizard/Abort.js
Normal file
58
src/views/Edit/Wizard/Abort.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Abort(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={8} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={3} variant="h1" title={<Trans>Abort</Trans>} onHelp={props.onHelp} />
|
||||
<Grid container spacing={3}>
|
||||
{props.nchannels <= 1 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>You can't abort the wizard because at least one input must be defined.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Are you sure you want to abort the wizard?</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onNext}>
|
||||
<Trans>Yes</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={props.onBack}>
|
||||
<Trans>No</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Abort.defaultProps = {
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
nchannels: 0,
|
||||
};
|
||||
136
src/views/Edit/Wizard/Audio.js
Normal file
136
src/views/Edit/Wizard/Audio.js
Normal file
@ -0,0 +1,136 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { useLingui } from '@lingui/react';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import RadioGroup from '@mui/material/RadioGroup';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import BoxText from '../../../misc/BoxText';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
import Select from '../../../misc/Select';
|
||||
|
||||
export default function Audio(props) {
|
||||
const { i18n } = useLingui();
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Audio setup</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{props.status === 'error' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>Failed to verify the source. Please check the address.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{props.status === 'nostream' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any audio streams.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{props.status === 'nocoder' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any compatible audio streams.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroup row value={props.source} onChange={props.onSource}>
|
||||
<Grid container spacing={2}>
|
||||
{props.streamList.length === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
The video source doesn't provide any compatible audio stream. <strong>Silence audio</strong> is recommended.
|
||||
Services e.g. YouTube, Facebook & Co. require an audio channel.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
{props.streamList.length !== 0 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel value="video" control={<Radio />} label={i18n._(t`Audio from device`)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Stream</Trans>} value={props.stream} onChange={props.onAudioStreamChange}>
|
||||
{props.streamList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{props.deviceList.length !== 0 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel value="alsa" control={<Radio />} label={i18n._(t`Audio from device`)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Device</Trans>} value={props.address} onChange={props.onAudioDeviceChange}>
|
||||
{props.deviceList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<div>
|
||||
<FormControlLabel value="silence" control={<Radio />} label={i18n._(t`Silence Audio`)} />
|
||||
</div>
|
||||
<div>
|
||||
<FormControlLabel value="none" control={<Radio />} label={i18n._(t`No audio`)} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={props.onNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Audio.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
onSource: () => {},
|
||||
source: '',
|
||||
onAudioStreamChange: () => {},
|
||||
onAudioDeviceChange: () => {},
|
||||
streamList: [],
|
||||
deviceList: [],
|
||||
status: '',
|
||||
stream: 0,
|
||||
address: {},
|
||||
};
|
||||
38
src/views/Edit/Wizard/Error.js
Normal file
38
src/views/Edit/Wizard/Error.js
Normal file
@ -0,0 +1,38 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import Button from '@mui/material/Button';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import BoxText from '../../../misc/BoxText';
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Error(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={8} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={3} variant="h1" title={<Trans>Error</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={3}>
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>There was an error setting up the stream.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={props.onNext}>
|
||||
<Trans>Retry</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Error.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onNext: () => {},
|
||||
};
|
||||
58
src/views/Edit/Wizard/License.js
Normal file
58
src/views/Edit/Wizard/License.js
Normal file
@ -0,0 +1,58 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import LicenseControl from '../../../misc/controls/License';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function License(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>License</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
Use your copyright and choose the correct image license. Whether free for all or highly restricted. Briefly discuss what others are
|
||||
allowed to do with your image.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LicenseControl license={props.license} onChange={props.onChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={props.onNext}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
License.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
onChange: (license) => {},
|
||||
license: '',
|
||||
};
|
||||
55
src/views/Edit/Wizard/Metadata.js
Normal file
55
src/views/Edit/Wizard/Metadata.js
Normal file
@ -0,0 +1,55 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import MetadataControl from '../../../misc/controls/Metadata';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Metadata(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Metadata</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Briefly describe what the audience will see during the live stream.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<MetadataControl settings={props.metadata} onChange={props.onChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={props.onNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Metadata.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
onChange: (metadata) => {},
|
||||
metadata: {},
|
||||
};
|
||||
31
src/views/Edit/Wizard/Probe.js
Normal file
31
src/views/Edit/Wizard/Probe.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Probe(props) {
|
||||
return (
|
||||
<Paper xs={12} md={5} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" onAbort={props.onAbort} />
|
||||
<Grid container justifyContent="center" spacing={2} align="center">
|
||||
<Grid item xs={12}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography textAlign="center">
|
||||
<Trans>Please wait. Probe stream data ...</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Probe.defaultProps = {
|
||||
onAbort: () => {},
|
||||
};
|
||||
31
src/views/Edit/Wizard/Saving.js
Normal file
31
src/views/Edit/Wizard/Saving.js
Normal file
@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Saving(props) {
|
||||
return (
|
||||
<Paper xs={12} md={5} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" onAbort={props.onAbort} />
|
||||
<Grid container justifyContent="center" spacing={2} align="center">
|
||||
<Grid item xs={12}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Please wait. Setting up the stream ...</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Saving.defaultProps = {
|
||||
onAbort: () => {},
|
||||
};
|
||||
51
src/views/Edit/Wizard/Source.js
Normal file
51
src/views/Edit/Wizard/Source.js
Normal file
@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Source(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={props.Abort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
Select whether you pull the stream from a <strong>network source</strong> (such as a network camera) or the{' '}
|
||||
<strong>internal RTMP server</strong> (e.g., OBS streams to the Restreamer).
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
{props.sources}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" fullWidth color="default" onClick={props.onAdvanced}>
|
||||
<Trans>Advanced setup</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Source.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onAdvanced: () => {},
|
||||
sources: [],
|
||||
};
|
||||
87
src/views/Edit/Wizard/Video.js
Normal file
87
src/views/Edit/Wizard/Video.js
Normal file
@ -0,0 +1,87 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Link from '@mui/material/Link';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import BoxText from '../../../misc/BoxText';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
|
||||
export default function Video(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
{props.children}
|
||||
<Grid item xs={12}>
|
||||
{props.status === 'error' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
{props.sourceid === 'rtmp' || props.sourceid === 'hls' ? (
|
||||
<Trans>No live stream was detected. Please check the software that sends the stream.</Trans>
|
||||
) : (
|
||||
<Trans>Failed to verify the source. Please check the address.</Trans>
|
||||
)}
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{props.status === 'nostream' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any video streams. Please check the device.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{props.status === 'nocoder' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>
|
||||
The source doesn't provide any compatible video streams. Please check the{' '}
|
||||
<Link color="secondary" target="_blank" href="https://github.com/datarhei/restreamer">
|
||||
requirements
|
||||
</Link>
|
||||
.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" disabled={!props.ready} onClick={props.onNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
Video.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
sourceid: '',
|
||||
status: '',
|
||||
ready: false,
|
||||
};
|
||||
101
src/views/Edit/Wizard/VideoProfile.js
Normal file
101
src/views/Edit/Wizard/VideoProfile.js
Normal file
@ -0,0 +1,101 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Trans } from '@lingui/macro';
|
||||
import Button from '@mui/material/Button';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
import Select from '../../../misc/Select';
|
||||
|
||||
export default function VideoProfile(props) {
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={props.onAbort} onHelp={props.onHelp} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>The video source is compatible. Select the desired resolution:</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Profile</Trans>} value={props.stream} onChange={props.onStreamChange}>
|
||||
{props.streamList}
|
||||
</Select>
|
||||
</Grid>
|
||||
{props.compatible === false && (
|
||||
<React.Fragment>
|
||||
{props.encodersList.length === 0 ? (
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Your stream needs to be encoded, but there's no suitable encoder available.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Your stream needs to be encoded. Choose the desired encoder:</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{props.decodersList.length >= 2 && (
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Decoder</Trans>} value={props.decoder} onChange={props.onDecoderChange}>
|
||||
{props.decodersList}
|
||||
</Select>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Encoder</Trans>} value={props.encoder} onChange={props.onEncoderChange}>
|
||||
{props.encodersList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={props.onBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
color="primary"
|
||||
disabled={props.compatible === false && props.encodersList.length === 0}
|
||||
onClick={props.onNext}
|
||||
>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
VideoProfile.defaultProps = {
|
||||
onAbort: () => {},
|
||||
onHelp: () => {},
|
||||
onBack: () => {},
|
||||
onNext: () => {},
|
||||
compatible: false,
|
||||
onStreamChange: () => {},
|
||||
streamList: [],
|
||||
stream: 0,
|
||||
onDecoderChange: () => {},
|
||||
decodersList: [],
|
||||
decoder: '',
|
||||
onEncoderChange: () => {},
|
||||
encodersList: [],
|
||||
encoder: '',
|
||||
};
|
||||
@ -2,33 +2,32 @@ import React from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
|
||||
import { useLingui } from '@lingui/react';
|
||||
import { Trans, t } from '@lingui/macro';
|
||||
import { t } from '@lingui/macro';
|
||||
import Backdrop from '@mui/material/Backdrop';
|
||||
import Button from '@mui/material/Button';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import Divider from '@mui/material/Divider';
|
||||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Link from '@mui/material/Link';
|
||||
import MenuItem from '@mui/material/MenuItem';
|
||||
import Radio from '@mui/material/Radio';
|
||||
import RadioGroup from '@mui/material/RadioGroup';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
|
||||
import * as Decoders from '../../../misc/coders/Decoders';
|
||||
import * as Encoders from '../../../misc/coders/Encoders';
|
||||
import * as M from '../../../utils/metadata';
|
||||
import BoxText from '../../../misc/BoxText';
|
||||
import FullSources from '../Sources';
|
||||
import H from '../../../utils/help';
|
||||
import LicenseControl from '../../../misc/controls/License';
|
||||
import MetadataControl from '../../../misc/controls/Metadata';
|
||||
import NotifyContext from '../../../contexts/Notify';
|
||||
import Paper from '../../../misc/Paper';
|
||||
import PaperHeader from '../../../misc/PaperHeader';
|
||||
import Sources from './Sources';
|
||||
import Select from '../../../misc/Select';
|
||||
|
||||
import Source from './Source';
|
||||
import Video from './Video';
|
||||
import VideoProfile from './VideoProfile';
|
||||
import Audio from './Audio';
|
||||
import Abort from './Abort';
|
||||
import Error from './Error';
|
||||
import Saving from './Saving';
|
||||
import Probe from './Probe';
|
||||
import License from './License';
|
||||
import Metadata from './Metadata';
|
||||
|
||||
export default function Wizard(props) {
|
||||
const { i18n } = useLingui();
|
||||
@ -281,37 +280,7 @@ export default function Wizard(props) {
|
||||
}
|
||||
|
||||
// STEP 1 - Source Type Selection
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={handleAbort} onHelp={handleHelp('video-setup')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
Select whether you pull the stream from a <strong>network source</strong> (such as a network camera) or the{' '}
|
||||
<strong>internal RTMP server</strong> (e.g., OBS streams to the Restreamer).
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid container spacing={2}>
|
||||
{availableSources}
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" fullWidth color="default" onClick={handleAdvanced}>
|
||||
<Trans>Advanced setup</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Source onAbort={handleAbort} onHelp={handleHelp('video-setup')} onAdvanced={handleAdvanced} sources={availableSources} />;
|
||||
} else if ($step === 'VIDEO SETTINGS') {
|
||||
handleNext = async () => {
|
||||
// probing ...
|
||||
@ -372,106 +341,45 @@ export default function Wizard(props) {
|
||||
|
||||
// STEP 2 - Source Settings
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={handleAbort} onHelp={handleHelp('video-settings')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Component
|
||||
knownDevices={$skills.sources[s.type]}
|
||||
config={$config.source[s.type]}
|
||||
settings={$sources.video.settings}
|
||||
skills={$skills}
|
||||
onChange={handleChange}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
<Grid item xs={12}>
|
||||
{$probe.status === 'error' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
{$sourceid === 'rtmp' || $sourceid === 'hls' ? (
|
||||
<Trans>No live stream was detected. Please check the software that sends the stream.</Trans>
|
||||
) : (
|
||||
<Trans>Failed to verify the source. Please check the address.</Trans>
|
||||
)}
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{$probe.status === 'nostream' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any video streams. Please check the device.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{$probe.status === 'nocoder' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>
|
||||
The source doesn't provide any compatible video streams. Please check the{' '}
|
||||
<Link color="secondary" target="_blank" href="https://github.com/datarhei/restreamer">
|
||||
requirements
|
||||
</Link>
|
||||
.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" disabled={!$sources.video.ready} onClick={handleNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Video
|
||||
onAbort={handleAbort}
|
||||
onHelp={handleHelp('video-settings')}
|
||||
onBack={handleBack}
|
||||
onNext={handleNext}
|
||||
status={$probe.status}
|
||||
sourceid={$sourceid}
|
||||
ready={$sources.video.ready}
|
||||
>
|
||||
<Component
|
||||
knownDevices={$skills.sources[s.type]}
|
||||
config={$config.source[s.type]}
|
||||
settings={$sources.video.settings}
|
||||
skills={$skills}
|
||||
onChange={handleChange}
|
||||
onRefresh={handleRefresh}
|
||||
/>
|
||||
<Backdrop open={$skillsRefresh}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Backdrop>
|
||||
</Paper>
|
||||
</Video>
|
||||
);
|
||||
}
|
||||
// STEP 3 - Source Probe
|
||||
else if ($step === 'VIDEO PROBE') {
|
||||
return (
|
||||
<Paper xs={12} md={5} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" onAbort={handleAbort} />
|
||||
<Grid container justifyContent="center" spacing={2} align="center">
|
||||
<Grid item xs={12}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography textAlign="center">
|
||||
<Trans>Please wait. Probe stream data ...</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Probe onAbort={handleAbort} />;
|
||||
} else if ($step === 'VIDEO RESULT') {
|
||||
handleNext = () => {
|
||||
const streams = $sources.video.streams;
|
||||
const videoprofile = $profile.video;
|
||||
|
||||
const encoder = Encoders.Video.Get(videoprofile.encoder.coder);
|
||||
let defaults = encoder.defaults();
|
||||
let defaults = encoder.defaults({}, $skills);
|
||||
|
||||
videoprofile.encoder.settings = defaults.settings;
|
||||
videoprofile.encoder.mapping = defaults.mapping;
|
||||
|
||||
const decoder = Decoders.Video.Get(videoprofile.decoder.coder);
|
||||
defaults = decoder.defaults();
|
||||
defaults = decoder.defaults({}, $skills);
|
||||
|
||||
videoprofile.decoder.settings = defaults.settings;
|
||||
videoprofile.decoder.mapping = defaults.mapping;
|
||||
@ -613,68 +521,22 @@ export default function Wizard(props) {
|
||||
|
||||
// STEP 4 - Video Profile Selection
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Video setup</Trans>} onAbort={handleAbort} onHelp={handleHelp('video-result')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>The video source is compatible. Select the desired resolution:</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Profile</Trans>} value={$profile.video.stream} onChange={handleStreamChange}>
|
||||
{streamList}
|
||||
</Select>
|
||||
</Grid>
|
||||
{compatible === false && (
|
||||
<React.Fragment>
|
||||
{encodersList.length === 0 ? (
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Your stream needs to be encoded, but there's no suitable encoder available.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Your stream needs to be encoded. Choose the desired encoder:</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
{decodersList.length >= 2 && (
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Decoder</Trans>} value={$profile.video.decoder.coder} onChange={handleDecoderChange}>
|
||||
{decodersList}
|
||||
</Select>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Encoder</Trans>} value={$profile.video.encoder.coder} onChange={handleEncoderChange}>
|
||||
{encodersList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" disabled={compatible === false && encodersList.length === 0} onClick={handleNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<VideoProfile
|
||||
onAbort={handleAbort}
|
||||
onHelp={handleHelp('video-result')}
|
||||
onBack={handleBack}
|
||||
onNext={handleNext}
|
||||
compatible={compatible}
|
||||
stream={$profile.video.stream}
|
||||
streamList={streamList}
|
||||
onStreamChange={handleStreamChange}
|
||||
decoder={$profile.video.decoder.coder}
|
||||
decodersList={decodersList}
|
||||
onDecoderChange={handleDecoderChange}
|
||||
encoder={$profile.video.encoder.coder}
|
||||
encodersList={encodersList}
|
||||
onEncoderChange={handleEncoderChange}
|
||||
/>
|
||||
);
|
||||
} else if ($step === 'AUDIO SETTINGS') {
|
||||
handleNext = async () => {
|
||||
@ -845,118 +707,24 @@ export default function Wizard(props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Audio setup</Trans>} onAbort={handleAbort} onHelp={handleHelp('audio-settings')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{$probe.status === 'error' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>Failed to verify the source. Please check the address.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{$probe.status === 'nostream' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any audio streams.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
{$probe.status === 'nocoder' && (
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>The source doesn't provide any compatible audio streams.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<RadioGroup row value={radioValue} onChange={handleStream}>
|
||||
<Grid container spacing={2}>
|
||||
{streamList.length === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
The video source doesn't provide any compatible audio stream. <strong>Silence audio</strong> is recommended.
|
||||
Services e.g. YouTube, Facebook & Co. require an audio channel.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
{streamList.length !== 0 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel value="video" control={<Radio />} label={i18n._(t`Audio from device`)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Stream</Trans>} value={profile.stream} onChange={handleAudioStreamChange}>
|
||||
{streamList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
{deviceList.length !== 0 && (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel value="alsa" control={<Radio />} label={i18n._(t`Audio from device`)} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Select label={<Trans>Device</Trans>} value={source.settings.address} onChange={handleAudioDeviceChange}>
|
||||
{deviceList}
|
||||
</Select>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<div>
|
||||
<FormControlLabel value="silence" control={<Radio />} label={i18n._(t`Silence Audio`)} />
|
||||
</div>
|
||||
<div>
|
||||
<FormControlLabel value="none" control={<Radio />} label={i18n._(t`No audio`)} />
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</RadioGroup>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={handleNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<Audio
|
||||
onAbort={handleAbort}
|
||||
onHelp={handleHelp('audio-settings')}
|
||||
onBack={handleBack}
|
||||
onNext={handleNext}
|
||||
status={$probe.status}
|
||||
source={radioValue}
|
||||
onSource={handleStream}
|
||||
streamList={streamList}
|
||||
deviceList={deviceList}
|
||||
stream={profile.stream}
|
||||
onAudioStreamChange={handleAudioStreamChange}
|
||||
address={source.settings.address}
|
||||
onAudioDeviceChange={handleAudioDeviceChange}
|
||||
/>
|
||||
);
|
||||
} else if ($step === 'AUDIO PROBE') {
|
||||
return (
|
||||
<Paper xs={12} md={5} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" onAbort={handleAbort} />
|
||||
<Grid container justifyContent="center" spacing={2} align="center">
|
||||
<Grid item xs={12}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Please wait. Probe stream data ...</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Probe onAbort={handleAbort} />;
|
||||
} else if ($step === 'AUDIO RESULT') {
|
||||
handleNext = () => {
|
||||
let stream = null;
|
||||
@ -981,7 +749,7 @@ export default function Wizard(props) {
|
||||
}
|
||||
|
||||
const encoder = Encoders.Audio.Get(profile.coder);
|
||||
const defaults = encoder.defaults(stream);
|
||||
const defaults = encoder.defaults(stream, $skills);
|
||||
|
||||
profile.encoder.settings = defaults.settings;
|
||||
profile.encoder.mapping = defaults.mapping;
|
||||
@ -1023,35 +791,14 @@ export default function Wizard(props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>Metadata</Trans>} onAbort={handleAbort} onHelp={handleHelp('audio-result')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Briefly describe what the audience will see during the live stream.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<MetadataControl settings={$data.meta} onChange={handleMetadataChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={handleNext}>
|
||||
<Trans>Next</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<Metadata
|
||||
onAbort={handleAbort}
|
||||
onHelp={handleHelp('audio-result')}
|
||||
onBack={handleBack}
|
||||
onNext={handleNext}
|
||||
onChange={handleMetadataChange}
|
||||
metadata={$data.meta}
|
||||
/>
|
||||
);
|
||||
} else if ($step === 'LICENSE') {
|
||||
handleNext = async () => {
|
||||
@ -1078,55 +825,17 @@ export default function Wizard(props) {
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={9} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" title={<Trans>License</Trans>} onAbort={handleAbort} onHelp={handleHelp('license')} />
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>
|
||||
Use your copyright and choose the correct image license. Whether free for all or highly restricted. Briefly discuss what others
|
||||
are allowed to do with your image.
|
||||
</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<LicenseControl license={$data.license} onChange={handleLicenseChange} />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Divider />
|
||||
</Grid>
|
||||
<Grid item xs={3}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={handleNext}>
|
||||
<Trans>Save</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<License
|
||||
onAbort={handleAbort}
|
||||
onHelp={handleHelp('license')}
|
||||
onBack={handleBack}
|
||||
onNext={handleNext}
|
||||
onChange={handleLicenseChange}
|
||||
license={$data.license}
|
||||
/>
|
||||
);
|
||||
} else if ($step === 'SAVING') {
|
||||
return (
|
||||
<Paper xs={12} md={5} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={2} variant="h1" onAbort={handleAbort} />
|
||||
<Grid container justifyContent="center" spacing={2} align="center">
|
||||
<Grid item xs={12}>
|
||||
<CircularProgress color="inherit" />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Please wait. Setting up the stream ...</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Saving onAbort={handleAbort} />;
|
||||
} else if ($step === 'DONE') {
|
||||
return null;
|
||||
} else if ($step === 'ERROR') {
|
||||
@ -1134,24 +843,7 @@ export default function Wizard(props) {
|
||||
setStep('TYPE');
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={8} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={3} variant="h1" title={<Trans>Error</Trans>} onAbort={handleAbort} onHelp={handleHelp('error')} />
|
||||
<Grid container spacing={3}>
|
||||
<BoxText color="dark">
|
||||
<WarningIcon fontSize="large" color="error" />
|
||||
<Typography textAlign="center">
|
||||
<Trans>There was an error setting up the stream.</Trans>
|
||||
</Typography>
|
||||
</BoxText>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" fullWidth color="primary" onClick={handleNext}>
|
||||
<Trans>Retry</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Error onAbort={handleAbort} onHelp={handleHelp('error')} />;
|
||||
} else if ($step === 'ABORT') {
|
||||
const nchannels = props.restreamer.ListChannels().length;
|
||||
|
||||
@ -1169,46 +861,12 @@ export default function Wizard(props) {
|
||||
navigate(`/`);
|
||||
};
|
||||
|
||||
return (
|
||||
<Paper xs={12} sm={8} md={6} marginBottom="6em" className="PaperM">
|
||||
<PaperHeader spacing={3} variant="h1" title={<Trans>Abort</Trans>} onHelp={handleHelp('abort')} />
|
||||
<Grid container spacing={3}>
|
||||
{nchannels <= 1 ? (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>You can't abort the wizard because at least one input must be defined.</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={handleBack}>
|
||||
<Trans>Back</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
) : (
|
||||
<React.Fragment>
|
||||
<Grid item xs={12}>
|
||||
<Typography>
|
||||
<Trans>Are you sure you want to abort the wizard?</Trans>
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="outlined" color="default" fullWidth onClick={handleNext}>
|
||||
<Trans>Yes</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={6}>
|
||||
<Button variant="outlined" color="primary" fullWidth onClick={handleBack}>
|
||||
<Trans>No</Trans>
|
||||
</Button>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Paper>
|
||||
);
|
||||
return <Abort onHelp={handleHelp('abort')} onBack={handleBack} onNext={handleNext} nchannels={nchannels} />;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Wizard.defaultProps = {
|
||||
restreamer: null,
|
||||
};
|
||||
|
||||
906
src/views/Edit/Wizard/index.test.js
Normal file
906
src/views/Edit/Wizard/index.test.js
Normal file
@ -0,0 +1,906 @@
|
||||
import React from 'react';
|
||||
import { render, fireEvent, act, screen } from '../../../utils/testing';
|
||||
import '@testing-library/jest-dom';
|
||||
|
||||
import Wizard from './index';
|
||||
|
||||
const restreamer = {
|
||||
SelectChannel: () => {
|
||||
return 'test';
|
||||
},
|
||||
GetChannel: () => {
|
||||
return {
|
||||
id: 'test',
|
||||
name: 'test',
|
||||
};
|
||||
},
|
||||
Skills: () => {
|
||||
return {
|
||||
ffmpeg: {
|
||||
version: '5.1.2',
|
||||
},
|
||||
formats: {
|
||||
demuxers: ['rtsp'],
|
||||
},
|
||||
protocols: {
|
||||
input: ['http', 'https', 'rtmp', 'rtmps', 'srt'],
|
||||
},
|
||||
sources: {
|
||||
network: {},
|
||||
},
|
||||
encoders: {
|
||||
audio: ['copy', 'none', 'aac'],
|
||||
video: ['copy', 'none', 'libx264'],
|
||||
},
|
||||
decoders: {
|
||||
audio: ['default'],
|
||||
video: ['default'],
|
||||
},
|
||||
};
|
||||
},
|
||||
RefreshSkills: () => {},
|
||||
ConfigActive: () => {
|
||||
return {
|
||||
source: {
|
||||
network: {
|
||||
rtmp: {
|
||||
enabled: true,
|
||||
app: '/live',
|
||||
token: 'foobar',
|
||||
},
|
||||
srt: {
|
||||
enabled: true,
|
||||
token: 'foobar',
|
||||
passphrase: 'bazfoobazfoo',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
Probe: (id, inputs) => {
|
||||
let streams = [];
|
||||
|
||||
if (inputs[0].address === 'rtmp://localhost/live/external.stream?token=foobar') {
|
||||
streams.push({
|
||||
url: inputs[0].address,
|
||||
format: 'rtmp',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'video',
|
||||
codec: 'h264',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 0,
|
||||
layout: '',
|
||||
channels: 0,
|
||||
});
|
||||
streams.push({
|
||||
url: inputs[0].address,
|
||||
format: 'rtmp',
|
||||
index: 0,
|
||||
stream: 1,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'aac',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
} else if (inputs[0].address === 'srt://localhost?mode=caller&transtype=live&streamid=external,mode:request,token:foobar&passphrase=bazfoobazfoo') {
|
||||
streams.push({
|
||||
url: inputs[0].address,
|
||||
format: 'srt',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'video',
|
||||
codec: 'h264',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 0,
|
||||
layout: '',
|
||||
channels: 0,
|
||||
});
|
||||
streams.push({
|
||||
url: inputs[0].address,
|
||||
format: 'srt',
|
||||
index: 0,
|
||||
stream: 1,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'aac',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
} else if (inputs[0].address === 'anullsrc=r=44100:cl=stereo') {
|
||||
streams.push({
|
||||
url: inputs[0].address,
|
||||
format: 'lavfi',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'pcm',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
} else {
|
||||
const name = inputs[0].address.split('/').pop();
|
||||
const [video, audio] = name.split('-');
|
||||
|
||||
switch (video) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'h264':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'video',
|
||||
codec: 'h264',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 0,
|
||||
layout: '',
|
||||
channels: 0,
|
||||
});
|
||||
break;
|
||||
case 'other':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'video',
|
||||
codec: 'mjpeg',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 0,
|
||||
layout: '',
|
||||
channels: 0,
|
||||
});
|
||||
break;
|
||||
case 'unknown':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 0,
|
||||
language: 'und',
|
||||
type: 'video',
|
||||
codec: 'xxx',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 0,
|
||||
layout: '',
|
||||
channels: 0,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (audio) {
|
||||
case 'none':
|
||||
break;
|
||||
case 'aac':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 1,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'aac',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
break;
|
||||
case 'ogg':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 1,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'ogg',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
break;
|
||||
case 'unknown':
|
||||
streams.push({
|
||||
url: 'rtsp://127.0.0.1/live/stream',
|
||||
format: 'rtsp',
|
||||
index: 0,
|
||||
stream: 1,
|
||||
language: 'und',
|
||||
type: 'audio',
|
||||
codec: 'xxx',
|
||||
coder: '',
|
||||
bitrate_kbps: 0,
|
||||
duration_sec: 0,
|
||||
fps: 0,
|
||||
pix_fmt: 'yuvj420p',
|
||||
width: 1280,
|
||||
height: 720,
|
||||
sampling_hz: 44100,
|
||||
layout: 'stereo',
|
||||
channels: 2,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [{ streams: streams }, null];
|
||||
},
|
||||
UpsertIngest: (_channelid, global, inputs, outputs, control) => {
|
||||
return [{}, null];
|
||||
},
|
||||
SetIngestMetadata: (_channelid, data) => {},
|
||||
UpsertIngestSnapshot: (_channelid, control) => {},
|
||||
UpdatePlayer: (_channelid) => {},
|
||||
UpdatePlayersite: () => {},
|
||||
};
|
||||
|
||||
test('wizard', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
expect(await screen.findByText(/Select whether you pull the stream from a/)).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('Network source')).toBeInTheDocument();
|
||||
expect(screen.queryByText('RTMP server')).toBeInTheDocument();
|
||||
expect(screen.queryByText('SRT server')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: rtmp source video h264-aac', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'RTMP server' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source is compatible. Select the desired resolution:/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Your stream needs to be encoded. Choose the desired encoder:/)).not.toBeInTheDocument();
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
|
||||
// Confirm metadata
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/License/)).toBeInTheDocument();
|
||||
|
||||
// Confirm license
|
||||
button = screen.getByRole('button', { name: 'Save' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
});
|
||||
|
||||
test('wizard: srt source video h264-aac', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'SRT server' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source is compatible. Select the desired resolution:/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Your stream needs to be encoded. Choose the desired encoder:/)).not.toBeInTheDocument();
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
|
||||
// Confirm metadata
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/License/)).toBeInTheDocument();
|
||||
|
||||
// Confirm license
|
||||
button = screen.getByRole('button', { name: 'Save' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
});
|
||||
|
||||
test('wizard: network source error', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/none-none' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Failed to verify the source. Please check the address./)).toBeInTheDocument();
|
||||
|
||||
// Add a stream address
|
||||
input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/none-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The source doesn't provide any video streams. Please check the device./)).toBeInTheDocument();
|
||||
|
||||
/*
|
||||
// TODO: This message appears only if there's no h264 encoder. However it should appear if there's no
|
||||
// suitable decoder for the detected codec. The question is, if this is actually possible, because
|
||||
// if FFmpeg doesn't know the codec, how can it detect it?
|
||||
|
||||
input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/unknown-none' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The source doesn't provide any compatible video streams./)).toBeInTheDocument();
|
||||
*/
|
||||
});
|
||||
|
||||
test('wizard: network source video h264', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-none' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source is compatible. Select the desired resolution:/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Your stream needs to be encoded. Choose the desired encoder:/)).not.toBeInTheDocument();
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source doesn't provide any compatible audio stream./)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: network source video non-h264', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/other-none' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source is compatible. Select the desired resolution:/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Your stream needs to be encoded. Choose the desired encoder:/)).toBeInTheDocument();
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/The video source doesn't provide any compatible audio stream./)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: network source audio aac', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
// Select suggested audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: network source audio non-aac', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-ogg' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
// Select suggested audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: network source silence audio', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByLabelText('Audio from device')).toBeChecked();
|
||||
expect(screen.queryByLabelText('Silence Audio')).not.toBeChecked();
|
||||
expect(screen.queryByLabelText('No audio')).not.toBeChecked();
|
||||
|
||||
input = screen.queryByLabelText('Silence Audio');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(input);
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText('Audio from device')).not.toBeChecked();
|
||||
expect(screen.queryByLabelText('Silence Audio')).toBeChecked();
|
||||
expect(screen.queryByLabelText('No audio')).not.toBeChecked();
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: network source no audio', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Audio from device/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/Silence Audio/)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/No audio/)).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByLabelText('Audio from device')).toBeChecked();
|
||||
expect(screen.queryByLabelText('Silence Audio')).not.toBeChecked();
|
||||
expect(screen.queryByLabelText('No audio')).not.toBeChecked();
|
||||
|
||||
input = screen.queryByLabelText('No audio');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(input);
|
||||
});
|
||||
|
||||
expect(screen.queryByLabelText('Audio from device')).not.toBeChecked();
|
||||
expect(screen.queryByLabelText('Silence Audio')).not.toBeChecked();
|
||||
expect(screen.queryByLabelText('No audio')).toBeChecked();
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: metadata', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/Metadata/)).toBeInTheDocument();
|
||||
expect(screen.queryByLabelText('Name')).toHaveValue('test');
|
||||
expect(screen.queryByLabelText('Description')).toHaveValue('Live from earth. Powered by datarhei Restreamer.');
|
||||
|
||||
// Confirm metadata
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/License/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
test('wizard: license', async () => {
|
||||
await act(async () => {
|
||||
render(<Wizard restreamer={restreamer} />, {}, '/wizard/test', '/wizard/:channelid');
|
||||
});
|
||||
|
||||
// Choose network source
|
||||
let button = screen.getByRole('button', { name: 'Network source' });
|
||||
fireEvent.click(button);
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeDisabled();
|
||||
|
||||
// Add a stream address
|
||||
let input = screen.getByLabelText('Address');
|
||||
fireEvent.change(input, { target: { value: 'rtsp://127.0.0.1/live/h264-aac' } });
|
||||
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Select suggested video stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Confirm selected audio stream
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
// Confirm metadata
|
||||
button = screen.getByRole('button', { name: 'Next' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
|
||||
expect(screen.queryByText(/License/)).toBeInTheDocument();
|
||||
|
||||
// Confirm license
|
||||
button = screen.getByRole('button', { name: 'Save' });
|
||||
expect(button).toBeEnabled();
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(button);
|
||||
});
|
||||
});
|
||||
@ -139,8 +139,8 @@ export default function Add(props) {
|
||||
const serviceSkills = helper.conflateServiceSkills(s.requires, $skills);
|
||||
|
||||
const profiles = $settings.profiles;
|
||||
profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', $sources[0].streams, serviceSkills.codecs.video, $skills.encoders.video);
|
||||
profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', $sources[0].streams, serviceSkills.codecs.audio, $skills.encoders.audio);
|
||||
profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', $sources[0].streams, serviceSkills.codecs.video, $skills);
|
||||
profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', $sources[0].streams, serviceSkills.codecs.audio, $skills);
|
||||
|
||||
setSettings({
|
||||
...$settings,
|
||||
|
||||
@ -162,8 +162,8 @@ export default function Edit(props) {
|
||||
const settings = await props.restreamer.GetEgressMetadata(_channelid, id);
|
||||
|
||||
const profiles = settings.profiles;
|
||||
profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', ingest.streams, serviceSkills.codecs.video, skills.encoders.video);
|
||||
profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', ingest.streams, serviceSkills.codecs.audio, skills.encoders.audio);
|
||||
profiles[0].video = helper.preselectProfile(profiles[0].video, 'video', ingest.streams, serviceSkills.codecs.video, skills);
|
||||
profiles[0].audio = helper.preselectProfile(profiles[0].audio, 'audio', ingest.streams, serviceSkills.codecs.audio, skills);
|
||||
|
||||
settings.profiles = profiles;
|
||||
settings.streams = M.createOutputStreams(sources, profiles);
|
||||
|
||||
@ -237,10 +237,12 @@ export function conflateServiceSkills(requires, skills) {
|
||||
* @param {*} type Either 'audio' or 'video'
|
||||
* @param {*} streams List of available streams
|
||||
* @param {*} codecs List of target codecs
|
||||
* @param {*} encoders list of available (to ffmpeg) encoders
|
||||
* @param {*} skills FFmpeg skills
|
||||
* @returns {boolean} Whether the provided profile is valid
|
||||
*/
|
||||
export function preselectProfile(profile, type, streams, codecs, encoders) {
|
||||
export function preselectProfile(profile, type, streams, codecs, skills) {
|
||||
const encoders = skills.encoders[type];
|
||||
|
||||
/**
|
||||
* Checks if the given profile makes sense, i.e. matches to the available
|
||||
* streams and codecs.
|
||||
@ -325,7 +327,7 @@ export function preselectProfile(profile, type, streams, codecs, encoders) {
|
||||
if (coder === null) {
|
||||
profile.encoder.coder = 'none';
|
||||
} else {
|
||||
const defaults = coder.defaults();
|
||||
const defaults = coder.defaults(streams[i], skills);
|
||||
profile.encoder.coder = coder.coder;
|
||||
profile.encoder.settings = defaults.settings;
|
||||
profile.encoder.mapping = defaults.mapping;
|
||||
@ -338,7 +340,7 @@ export function preselectProfile(profile, type, streams, codecs, encoders) {
|
||||
|
||||
let coder = type === 'audio' ? Coders.Audio.Get('copy') : Coders.Video.Get('copy');
|
||||
|
||||
const defaults = coder.defaults();
|
||||
const defaults = coder.defaults(streams[i], skills);
|
||||
|
||||
profile.encoder.settings = defaults.settings;
|
||||
profile.encoder.mapping = defaults.mapping;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user