Fix wrong call to encoder defaults (datarhei/restreamer#467)

This commit is contained in:
Ingo Oppermann 2022-11-22 16:49:21 +01:00
parent d6b19e0e0f
commit ec4edf2f47
No known key found for this signature in database
GPG Key ID: 2AB32426E9DD229E
54 changed files with 1860 additions and 615 deletions

View File

@ -15,6 +15,8 @@ import H from '../utils/help';
export default function EncodingSelect(props) {
const { i18n } = useLingui();
console.log(props.streams);
const profile = props.profile;
let availableEncoders = [];
let availableDecoders = [];
@ -28,6 +30,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 +42,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 +61,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 +73,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 +202,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}>

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: [],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,12 @@ const codecs = [];
const type = 'audio';
const hwaccel = false;
function defaults() {
function defaults(stream, skills) {
const settings = init({});
return {
settings: settings,
mapping: createMapping(settings),
mapping: createMapping(settings, stream, skills),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: [],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'h264_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -53,12 +53,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'h264_mmal'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'hevc_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mjpeg_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mpeg1_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mpeg2_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mpeg2_mmal'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mpeg4_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'mpeg4_mmal'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-hwaccel', 'cuda', '-hwaccel_output_format', 'cuda'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -53,12 +53,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'vc1_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'vc1_mmal'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'vp8_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-c:v', 'vp9_cuvid'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -8,7 +8,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const mapping = {
global: [],
local: ['-hwaccel', 'videotoolbox'],
@ -27,7 +27,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -51,12 +51,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'aac', '-b:a', `${settings.bitrate}k`, '-shortest'];
if (stream.codec === 'aac') {
@ -30,7 +30,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -39,7 +38,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -84,12 +83,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'aac_at', '-b:a', `${settings.bitrate}k`, '-shortest'];
if (stream.codec === 'aac') {
@ -30,7 +30,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -39,7 +38,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -84,12 +83,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),
};
}

View File

@ -1,6 +1,6 @@
import React from 'react';
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'copy'];
//if (stream.codec === 'aac') {
@ -17,7 +17,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = {};
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -26,7 +25,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -54,12 +53,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'libopus', '-b:a', `${settings.bitrate}k`, '-shortest'];
const mapping = {
@ -26,7 +26,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -35,7 +34,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -80,12 +79,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'libvorbis', '-b:a', `${settings.bitrate}k`, '-shortest'];
const mapping = {
@ -26,7 +26,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -35,7 +34,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -80,12 +79,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
// '-qscale:a', '6'
const local = ['-codec:a', 'libmp3lame', '-b:a', `${settings.bitrate}k`, '-shortest'];
@ -27,7 +27,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -36,7 +35,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -81,12 +80,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),
};
}

View File

@ -1,6 +1,6 @@
import React from 'react';
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-an'];
const mapping = {
@ -13,7 +13,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = {};
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -22,7 +21,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -50,12 +49,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),
};
}

View File

@ -19,7 +19,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
let sampling = settings.sampling;
let layout = settings.layout;
@ -90,7 +90,6 @@ Delay.defaultProps = {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -99,7 +98,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -147,12 +146,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),
};
}

View File

@ -13,7 +13,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-codec:a', 'vorbis', '-b:a', `${settings.bitrate}k`, '-qscale:a', '3', '-shortest'];
const mapping = {
@ -26,7 +26,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = init(props.settings);
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -35,7 +34,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -80,12 +79,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),
};
}

View File

@ -1,6 +1,6 @@
import React from 'react';
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const local = ['-codec:v', 'copy'];
const mapping = {
@ -21,7 +21,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -43,10 +43,10 @@ function summarize(settings) {
return `${name}`;
}
function defaults() {
function defaults(stream, skills) {
return {
settings: {},
mapping: createMapping({}),
mapping: createMapping({}, stream, skills),
};
}

View File

@ -23,7 +23,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const local = [
'-codec:v',
'h264_nvenc',
@ -171,7 +171,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -232,12 +232,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),
};
}

View File

@ -16,7 +16,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const local = [
'-codec:v',
'h264_omx',
@ -58,7 +58,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -110,12 +110,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),
};
}

View File

@ -75,7 +75,7 @@ Codec Controls
4: High
*/
function createMapping(settings, skills) {
function createMapping(settings, stream, skills) {
let ffversion = 4;
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
ffversion = 5;
@ -143,7 +143,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -216,12 +216,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),
};
}

View File

@ -23,7 +23,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const global = [];
const local = [];
@ -105,7 +105,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -163,12 +163,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),
};
}

View File

@ -21,7 +21,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const local = [
'-codec:v',
'h264_videotoolbox',
@ -84,7 +84,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -139,12 +139,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),
};
}

View File

@ -23,7 +23,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const global = [];
const local = [];
@ -105,7 +105,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -163,12 +163,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),
};
}

View File

@ -1,6 +1,6 @@
import React from 'react';
function createMapping(settings, stream) {
function createMapping(settings, stream, skills) {
const local = ['-vn'];
const mapping = {
@ -13,7 +13,6 @@ function createMapping(settings, stream) {
function Coder(props) {
const settings = {};
const stream = props.stream;
const handleChange = (newSettings) => {
let automatic = false;
@ -22,7 +21,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, stream), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -50,12 +49,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),
};
}

View File

@ -1,6 +1,6 @@
import React from 'react';
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const local = ['-codec:v', 'rawvideo'];
const mapping = {
@ -21,7 +21,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
React.useEffect(() => {
@ -43,10 +43,10 @@ function summarize(settings) {
return `${name}`;
}
function defaults() {
function defaults(stream, skills) {
return {
settings: {},
mapping: createMapping({}),
mapping: createMapping({}, stream, skills),
};
}

View File

@ -17,7 +17,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, skills) {
function createMapping(settings, stream, skills) {
let ffversion = 4;
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
ffversion = 5;
@ -75,7 +75,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -129,12 +129,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),
};
}

View File

@ -23,7 +23,7 @@ function init(initialState) {
return state;
}
function createMapping(settings) {
function createMapping(settings, stream, skills) {
const global = [];
const local = [];
@ -105,7 +105,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -163,12 +163,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),
};
}

View File

@ -24,7 +24,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, skills) {
function createMapping(settings, stream, skills) {
let ffversion = 4;
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
ffversion = 5;
@ -132,7 +132,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -195,12 +195,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),
};
}

View File

@ -24,7 +24,7 @@ function init(initialState) {
return state;
}
function createMapping(settings, skills) {
function createMapping(settings, stream, skills) {
let ffversion = 4;
if (SemverSatisfies(skills.ffmpeg.version, '^5.0.0')) {
ffversion = 5;
@ -132,7 +132,7 @@ function Coder(props) {
automatic = true;
}
props.onChange(newSettings, createMapping(newSettings, props.skills), automatic);
props.onChange(newSettings, createMapping(newSettings, props.stream, props.skills), automatic);
};
const update = (what) => (event) => {
@ -195,12 +195,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),
};
}

View File

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

View File

@ -2,7 +2,7 @@ import React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom';
import { ThemeProvider, StyledEngineProvider } from '@mui/material/styles';
import { HashRouter as DOMRouter } from 'react-router-dom';
import { MemoryRouter, Route, Routes } from 'react-router-dom';
import theme from '../theme';
import I18n from '../I18n';
@ -30,21 +30,60 @@ 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 AllTheProviders = ({ children }) => {
return (
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
<I18n>
<DOMRouter>{children}</DOMRouter>
</I18n>
</ThemeProvider>
</StyledEngineProvider>
);
const NoRoute = (props) => {
return null;
};
const customRender = (ui, options) => render(ui, { wrapper: AllTheProviders, ...options });
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';

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

View 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 &amp; 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: {},
};

View 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: () => {},
};

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

View 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: {},
};

View 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: () => {},
};

View 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: () => {},
};

View 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: [],
};

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

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

View File

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

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