Gw/videovariantfixes (#53)

* A fix for issue https://github.com/owncast/owncast/issues/770
- merge CPU slider functionality into main video variant form
- move slider constants into contants file for consistency and to reduce clutter on variant form file

* disable other forms and add a notice about vieo passthrough enabling

* Prettified Code!

* Tweak message text

* Prettified Code!

Co-authored-by: gingervitis <gingervitis@users.noreply.github.com>
Co-authored-by: Gabe Kangas <gabek@real-ity.com>
Co-authored-by: gabek <gabek@users.noreply.github.com>
This commit is contained in:
gingervitis 2021-02-26 23:44:13 -08:00 committed by GitHub
parent 679ed7cc56
commit 49269dd175
4 changed files with 178 additions and 201 deletions

View file

@ -1,76 +0,0 @@
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Slider } from 'antd';
import { ServerStatusContext } from '../../utils/server-status-context';
const { Title } = Typography;
const SLIDER_MARKS = {
1: 'lowest',
2: '',
3: '',
4: '',
5: 'highest',
};
const TOOLTIPS = {
1: 'Lowest CPU usage - lowest quality video',
2: 'Low CPU usage - low quality video',
3: 'Medium CPU usage - average quality video',
4: 'High CPU usage - high quality video',
5: 'Highest CPU usage - higher quality video',
};
interface Props {
defaultValue: number;
disabled: boolean;
onChange: (arg: number) => void;
}
export default function CPUUsageSelector({ defaultValue, disabled, onChange }: Props) {
const [selectedOption, setSelectedOption] = useState(null);
const serverStatusData = useContext(ServerStatusContext);
const { serverConfig } = serverStatusData || {};
const { videoSettings } = serverConfig || {};
if (!videoSettings) {
return null;
}
useEffect(() => {
setSelectedOption(defaultValue);
}, [videoSettings]);
const handleChange = value => {
setSelectedOption(value);
onChange(value);
};
const cpuUsageNote = () => {
if (disabled) {
return 'CPU usage selection is disabled when Video Passthrough is enabled.';
}
return TOOLTIPS[selectedOption];
};
return (
<div className="config-video-cpu-container">
<Title level={3}>CPU Usage</Title>
<p className="description">
Reduce to improve server performance, or increase it to improve video quality.
</p>
<div className="segment-slider-container">
<Slider
tipFormatter={value => TOOLTIPS[value]}
onChange={handleChange}
min={1}
max={Object.keys(SLIDER_MARKS).length}
marks={SLIDER_MARKS}
defaultValue={selectedOption}
value={selectedOption}
disabled={disabled}
/>
<p className="selected-value-note">{cpuUsageNote()}</p>
</div>
</div>
);
}

View file

@ -1,62 +1,25 @@
// This content populates the video variant modal, which is spawned from the variants table. // This content populates the video variant modal, which is spawned from the variants table. This relies on the `dataState` prop fed in by the table.
import React from 'react'; import React from 'react';
import { Popconfirm, Row, Col, Slider, Collapse, Typography } from 'antd'; import { Popconfirm, Row, Col, Slider, Collapse, Typography } from 'antd';
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import classNames from 'classnames';
import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section'; import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section';
import TextField from './form-textfield'; import TextField from './form-textfield';
import { DEFAULT_VARIANT_STATE } from '../../utils/config-constants'; import {
import CPUUsageSelector from './cpu-usage'; DEFAULT_VARIANT_STATE,
VIDEO_VARIANT_SETTING_DEFAULTS,
ENCODER_PRESET_SLIDER_MARKS,
ENCODER_PRESET_TOOLTIPS,
VIDEO_BITRATE_DEFAULTS,
VIDEO_BITRATE_SLIDER_MARKS,
FRAMERATE_SLIDER_MARKS,
FRAMERATE_DEFAULTS,
FRAMERATE_TOOLTIPS,
} from '../../utils/config-constants';
import ToggleSwitch from './form-toggleswitch'; import ToggleSwitch from './form-toggleswitch';
const { Panel } = Collapse; const { Panel } = Collapse;
const VIDEO_VARIANT_DEFAULTS = {
framerate: {
min: 24,
max: 120,
defaultValue: 24,
unit: 'fps',
incrementBy: null,
tip:
'Reducing your framerate will decrease the amount of video that needs to be encoded and sent to your viewers, saving CPU and bandwidth at the expense of smoothness. A lower value is generally is fine for most content.',
},
videoBitrate: {
min: 600,
max: 6000,
defaultValue: 1200,
unit: 'kbps',
incrementBy: 100,
tip: 'The overall quality of your stream is generally impacted most by bitrate.',
},
audioBitrate: {
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'nothing to see here',
},
videoPassthrough: {
tip: 'If enabled, all other settings will be disabled. Otherwise configure as desired.',
},
audioPassthrough: {
tip: 'If No is selected, then you should set your desired Audio Bitrate.',
},
scaledWidth: {
fieldName: 'scaledWidth',
label: 'Resized Width',
maxLength: 4,
placeholder: '1080',
tip: "Optionally resize this content's width.",
},
scaledHeight: {
fieldName: 'scaledHeight',
label: 'Resized Height',
maxLength: 4,
placeholder: '720',
tip: "Optionally resize this content's height.",
},
};
interface VideoVariantFormProps { interface VideoVariantFormProps {
dataState: VideoVariant; dataState: VideoVariant;
onUpdateField: FieldUpdaterFunc; onUpdateField: FieldUpdaterFunc;
@ -66,6 +29,8 @@ export default function VideoVariantForm({
dataState = DEFAULT_VARIANT_STATE, dataState = DEFAULT_VARIANT_STATE,
onUpdateField, onUpdateField,
}: VideoVariantFormProps) { }: VideoVariantFormProps) {
const videoPassthroughEnabled = dataState.videoPassthrough;
const handleFramerateChange = (value: number) => { const handleFramerateChange = (value: number) => {
onUpdateField({ fieldName: 'framerate', value }); onUpdateField({ fieldName: 'framerate', value });
}; };
@ -99,40 +64,17 @@ export default function VideoVariantForm({
// If passthrough is currently on, set it back to false on toggle. // If passthrough is currently on, set it back to false on toggle.
// Else let the Popconfirm turn it on. // Else let the Popconfirm turn it on.
const handleVideoPassthroughToggle = (value: boolean) => { const handleVideoPassthroughToggle = (value: boolean) => {
if (dataState.videoPassthrough) { if (videoPassthroughEnabled) {
onUpdateField({ fieldName: 'videoPassthrough', value }); onUpdateField({ fieldName: 'videoPassthrough', value });
} }
}; };
const framerateDefaults = VIDEO_VARIANT_DEFAULTS.framerate; // Slider notes
const framerateMin = framerateDefaults.min;
const framerateMax = framerateDefaults.max;
const framerateUnit = framerateDefaults.unit;
const framerateMarks = {
[framerateMin]: `${framerateMin} ${framerateUnit}`,
30: '',
60: '',
90: '',
[framerateMax]: `${framerateMax} ${framerateUnit}`,
};
const videoBitrateDefaults = VIDEO_VARIANT_DEFAULTS.videoBitrate;
const videoBRMin = videoBitrateDefaults.min;
const videoBRMax = videoBitrateDefaults.max;
const videoBRUnit = videoBitrateDefaults.unit;
const videoBRMarks = {
[videoBRMin]: `${videoBRMin} ${videoBRUnit}`,
3000: 3000,
4500: 4500,
[videoBRMax]: `${videoBRMax} ${videoBRUnit}`,
};
const selectedVideoBRnote = () => { const selectedVideoBRnote = () => {
if (dataState.videoPassthrough) { if (videoPassthroughEnabled) {
return 'Bitrate selection is disabled when Video Passthrough is enabled.'; return 'Bitrate selection is disabled when Video Passthrough is enabled.';
} }
let note = `${dataState.videoBitrate}${VIDEO_BITRATE_DEFAULTS.unit}`;
let note = `${dataState.videoBitrate}${videoBRUnit}`;
if (dataState.videoBitrate < 2000) { if (dataState.videoBitrate < 2000) {
note = `${note} - Good for low bandwidth environments.`; note = `${note} - Good for low bandwidth environments.`;
} else if (dataState.videoBitrate < 3500) { } else if (dataState.videoBitrate < 3500) {
@ -143,35 +85,24 @@ export default function VideoVariantForm({
return note; return note;
}; };
const selectedFramerateNote = () => { const selectedFramerateNote = () => {
if (dataState.videoPassthrough) { if (videoPassthroughEnabled) {
return 'Framerate selection is disabled when Video Passthrough is enabled.'; return 'Framerate selection is disabled when Video Passthrough is enabled.';
} }
return FRAMERATE_TOOLTIPS[dataState.framerate] || '';
let note = `Selected: ${dataState.framerate}${framerateUnit}`; };
switch (dataState.framerate) { const cpuUsageNote = () => {
case 24: if (videoPassthroughEnabled) {
note = `${note} - Good for film, presentations, music, low power/bandwidth servers.`; return 'CPU usage selection is disabled when Video Passthrough is enabled.';
break;
case 30:
note = `${note} - Good for slow/casual games, chat, general purpose.`;
break;
case 60:
note = `${note} - Good for fast/action games, sports, HD video.`;
break;
case 90:
note = `${note} - Good for newer fast games and hardware.`;
break;
case 120:
note = `${note} - Experimental, use at your own risk!`;
break;
default:
note = '';
} }
return note; return ENCODER_PRESET_TOOLTIPS[dataState.cpuUsageLevel] || '';
}; };
const classes = classNames({
'config-variant-form': true,
'video-passthrough-enabled': videoPassthroughEnabled,
});
return ( return (
<div className="config-variant-form"> <div className={classes}>
<p className="description"> <p className="description">
<a href="https://owncast.online/docs/video" target="_blank" rel="noopener noreferrer"> <a href="https://owncast.online/docs/video" target="_blank" rel="noopener noreferrer">
Learn more Learn more
@ -179,15 +110,34 @@ export default function VideoVariantForm({
about how each of these settings can impact the performance of your server. about how each of these settings can impact the performance of your server.
</p> </p>
{videoPassthroughEnabled && (
<p className="passthrough-warning">
NOTE: Video Passthrough for this output stream variant is <em>enabled</em>, disabling the
below video encoding settings.
</p>
)}
<Row gutter={16}> <Row gutter={16}>
<Col sm={24} md={12}> <Col sm={24} md={12}>
{/* ENCODER PRESET FIELD */} {/* ENCODER PRESET (CPU USAGE) FIELD */}
<div className="form-module cpu-usage-container"> <div className="form-module cpu-usage-container">
<CPUUsageSelector <Typography.Title level={3}>CPU Usage</Typography.Title>
defaultValue={dataState.cpuUsageLevel} <p className="description">
onChange={handleVideoCpuUsageLevelChange} Reduce to improve server performance, or increase it to improve video quality.
disabled={dataState.videoPassthrough} </p>
/> <div className="segment-slider-container">
<Slider
tipFormatter={value => ENCODER_PRESET_TOOLTIPS[value]}
onChange={handleVideoCpuUsageLevelChange}
min={1}
max={Object.keys(ENCODER_PRESET_SLIDER_MARKS).length}
marks={ENCODER_PRESET_SLIDER_MARKS}
defaultValue={dataState.cpuUsageLevel}
value={dataState.cpuUsageLevel}
disabled={dataState.videoPassthrough}
/>
<p className="selected-value-note">{cpuUsageNote()}</p>
</div>
<p className="read-more-subtext"> <p className="read-more-subtext">
<a <a
href="https://owncast.online/docs/video/#cpu-usage" href="https://owncast.online/docs/video/#cpu-usage"
@ -208,18 +158,18 @@ export default function VideoVariantForm({
}`} }`}
> >
<Typography.Title level={3}>Video Bitrate</Typography.Title> <Typography.Title level={3}>Video Bitrate</Typography.Title>
<p className="description">{VIDEO_VARIANT_DEFAULTS.videoBitrate.tip}</p> <p className="description">{VIDEO_BITRATE_DEFAULTS.tip}</p>
<div className="segment-slider-container"> <div className="segment-slider-container">
<Slider <Slider
tipFormatter={value => `${value} ${videoBRUnit}`} tipFormatter={value => `${value} ${VIDEO_BITRATE_DEFAULTS.unit}`}
disabled={dataState.videoPassthrough} disabled={dataState.videoPassthrough}
defaultValue={dataState.videoBitrate} defaultValue={dataState.videoBitrate}
value={dataState.videoBitrate} value={dataState.videoBitrate}
onChange={handleVideoBitrateChange} onChange={handleVideoBitrateChange}
step={videoBitrateDefaults.incrementBy} step={VIDEO_BITRATE_DEFAULTS.incrementBy}
min={videoBRMin} min={VIDEO_BITRATE_DEFAULTS.min}
max={videoBRMax} max={VIDEO_BITRATE_DEFAULTS.max}
marks={videoBRMarks} marks={VIDEO_BITRATE_SLIDER_MARKS}
/> />
<p className="selected-value-note">{selectedVideoBRnote()}</p> <p className="selected-value-note">{selectedVideoBRnote()}</p>
</div> </div>
@ -256,14 +206,14 @@ export default function VideoVariantForm({
<br /> <br />
<TextField <TextField
type="number" type="number"
{...VIDEO_VARIANT_DEFAULTS.scaledWidth} {...VIDEO_VARIANT_SETTING_DEFAULTS.scaledWidth}
value={dataState.scaledWidth} value={dataState.scaledWidth}
onChange={handleScaledWidthChanged} onChange={handleScaledWidthChanged}
disabled={dataState.videoPassthrough} disabled={dataState.videoPassthrough}
/> />
<TextField <TextField
type="number" type="number"
{...VIDEO_VARIANT_DEFAULTS.scaledHeight} {...VIDEO_VARIANT_SETTING_DEFAULTS.scaledHeight}
value={dataState.scaledHeight} value={dataState.scaledHeight}
onChange={handleScaledHeightChanged} onChange={handleScaledHeightChanged}
disabled={dataState.videoPassthrough} disabled={dataState.videoPassthrough}
@ -272,7 +222,7 @@ export default function VideoVariantForm({
</Col> </Col>
<Col sm={24} md={12}> <Col sm={24} md={12}>
{/* VIDEO PASSTHROUGH FIELD */} {/* VIDEO PASSTHROUGH FIELD */}
<div className="form-module video-passthroug-module"> <div className="form-module video-passthrough-module">
<Typography.Title level={3}>Video Passthrough</Typography.Title> <Typography.Title level={3}>Video Passthrough</Typography.Title>
<p className="description"> <p className="description">
<p> <p>
@ -307,7 +257,7 @@ export default function VideoVariantForm({
<ToggleSwitch <ToggleSwitch
label="Use Video Passthrough?" label="Use Video Passthrough?"
fieldName="video-passthrough" fieldName="video-passthrough"
tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} tip={VIDEO_VARIANT_SETTING_DEFAULTS.videoPassthrough.tip}
checked={dataState.videoPassthrough} checked={dataState.videoPassthrough}
onChange={handleVideoPassthroughToggle} onChange={handleVideoPassthroughToggle}
/> />
@ -320,17 +270,17 @@ export default function VideoVariantForm({
{/* FRAME RATE FIELD */} {/* FRAME RATE FIELD */}
<div className="form-module frame-rate-module"> <div className="form-module frame-rate-module">
<Typography.Title level={3}>Frame rate</Typography.Title> <Typography.Title level={3}>Frame rate</Typography.Title>
<p className="description">{VIDEO_VARIANT_DEFAULTS.framerate.tip}</p> <p className="description">{FRAMERATE_DEFAULTS.tip}</p>
<div className="segment-slider-container"> <div className="segment-slider-container">
<Slider <Slider
tipFormatter={value => `${value} ${framerateUnit}`} tipFormatter={value => `${value} ${FRAMERATE_DEFAULTS.unit}`}
defaultValue={dataState.framerate} defaultValue={dataState.framerate}
value={dataState.framerate} value={dataState.framerate}
onChange={handleFramerateChange} onChange={handleFramerateChange}
step={framerateDefaults.incrementBy} step={FRAMERATE_DEFAULTS.incrementBy}
min={framerateMin} min={FRAMERATE_DEFAULTS.min}
max={framerateMax} max={FRAMERATE_DEFAULTS.max}
marks={framerateMarks} marks={FRAMERATE_SLIDER_MARKS}
disabled={dataState.videoPassthrough} disabled={dataState.videoPassthrough}
/> />
<p className="selected-value-note">{selectedFramerateNote()}</p> <p className="selected-value-note">{selectedFramerateNote()}</p>

View file

@ -15,6 +15,15 @@
.description { .description {
margin-top: 0; margin-top: 0;
} }
.passthrough-warning {
text-align: center;
padding: 1em;
color: var(--ant-warning);
font-size: 0.88em;
font-weight: 500;
background-color: var(--black-50);
border-radius: var(--container-border-radius);
}
.cpu-usage-container, .cpu-usage-container,
.bitrate-container { .bitrate-container {
@ -25,10 +34,17 @@
margin-top: 1em; margin-top: 1em;
.resolution-module, .resolution-module,
.video-passthroug-module { .video-passthrough-module {
min-height: 30em; min-height: 30em;
} }
} }
// make some things look disabled when passthrough is on
&.video-passthrough-enabled {
.form-module:not(.video-passthrough-module) {
opacity: 0.25;
cursor: not-allowed;
}
}
} }
.variants-table { .variants-table {

View file

@ -144,8 +144,7 @@ export const FIELD_PROPS_YP = {
apiPath: API_YP_SWITCH, apiPath: API_YP_SWITCH,
configPath: 'yp', configPath: 'yp',
label: 'Enable directory', label: 'Enable directory',
tip: tip: 'Turn this ON if you want to show up in the directory.',
'Turn this ON if you want to show up in the directory.',
}; };
export const DEFAULT_VARIANT_STATE: VideoVariant = { export const DEFAULT_VARIANT_STATE: VideoVariant = {
@ -159,6 +158,94 @@ export const DEFAULT_VARIANT_STATE: VideoVariant = {
scaledWidth: null, scaledWidth: null,
}; };
export const VIDEO_VARIANT_SETTING_DEFAULTS = {
// this one is currently unused
audioBitrate: {
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'nothing to see here',
},
videoPassthrough: {
tip: 'If enabled, all other settings will be disabled. Otherwise configure as desired.',
},
audioPassthrough: {
tip: 'If No is selected, then you should set your desired Audio Bitrate.',
},
scaledWidth: {
fieldName: 'scaledWidth',
label: 'Resized Width',
maxLength: 4,
placeholder: '1080',
tip: "Optionally resize this content's width.",
},
scaledHeight: {
fieldName: 'scaledHeight',
label: 'Resized Height',
maxLength: 4,
placeholder: '720',
tip: "Optionally resize this content's height.",
},
};
// VIDEO VARIANT FORM - framerate
export const FRAMERATE_DEFAULTS = {
min: 24,
max: 120,
defaultValue: 24,
unit: 'fps',
incrementBy: null,
tip:
'Reducing your framerate will decrease the amount of video that needs to be encoded and sent to your viewers, saving CPU and bandwidth at the expense of smoothness. A lower value is generally is fine for most content.',
};
export const FRAMERATE_SLIDER_MARKS = {
[FRAMERATE_DEFAULTS.min]: `${FRAMERATE_DEFAULTS.min} ${FRAMERATE_DEFAULTS.unit}`,
30: '',
60: '',
90: '',
[FRAMERATE_DEFAULTS.max]: `${FRAMERATE_DEFAULTS.max} ${FRAMERATE_DEFAULTS.unit}`,
};
export const FRAMERATE_TOOLTIPS = {
[FRAMERATE_DEFAULTS.min]: `${FRAMERATE_DEFAULTS.min}fps - Good for film, presentations, music, low power/bandwidth servers.`,
30: '30fps - Good for slow/casual games, chat, general purpose.',
60: '60fps - Good for fast/action games, sports, HD video.',
90: '90fps - Good for newer fast games and hardware.',
[FRAMERATE_DEFAULTS.max]: `${FRAMERATE_DEFAULTS.max}fps - Experimental, use at your own risk!`,
};
// VIDEO VARIANT FORM - bitrate
export const VIDEO_BITRATE_DEFAULTS = {
min: 600,
max: 6000,
defaultValue: 1200,
unit: 'kbps',
incrementBy: 100,
tip: 'The overall quality of your stream is generally impacted most by bitrate.',
};
export const VIDEO_BITRATE_SLIDER_MARKS = {
[VIDEO_BITRATE_DEFAULTS.min]: `${VIDEO_BITRATE_DEFAULTS.min} ${VIDEO_BITRATE_DEFAULTS.unit}`,
3000: 3000,
4500: 4500,
[VIDEO_BITRATE_DEFAULTS.max]: `${VIDEO_BITRATE_DEFAULTS.max} ${VIDEO_BITRATE_DEFAULTS.unit}`,
};
// VIDEO VARIANT FORM - encoder preset
// CPU
export const ENCODER_PRESET_SLIDER_MARKS = {
1: 'lowest',
2: '',
3: '',
4: '',
5: 'highest',
};
export const ENCODER_PRESET_TOOLTIPS = {
1: 'Lowest CPU usage - lowest quality video',
2: 'Low CPU usage - low quality video',
3: 'Medium CPU usage - average quality video',
4: 'High CPU usage - high quality video',
5: 'Highest CPU usage - higher quality video',
};
export const DEFAULT_SOCIAL_HANDLE: SocialHandle = { export const DEFAULT_SOCIAL_HANDLE: SocialHandle = {
url: '', url: '',
platform: '', platform: '',