0

form fields for video config modal

This commit is contained in:
gingervitis 2021-01-10 02:37:22 -08:00 committed by Gabe Kangas
parent 61e172908a
commit 8458849d88
8 changed files with 481 additions and 49 deletions

View File

@ -2,7 +2,7 @@
import React from 'react';
import { CheckCircleFilled, ExclamationCircleFilled } from '@ant-design/icons';
import { fetchData, SERVER_CONFIG_UPDATE_URL } from '../../../utils/apis';
import { ApiPostArgs } from '../../../types/config-section';
import { ApiPostArgs, VideoVariant } from '../../../types/config-section';
export const DEFAULT_NAME = 'Owncast User';
export const DEFAULT_TITLE = 'Owncast Server';
@ -214,3 +214,60 @@ export const TEXTFIELD_DEFAULTS = {
}
}
export const ENCODER_PRESETS = [
'fast',
'faster',
'veryfast',
'superfast',
'ultrafast',
];
export const DEFAULT_VARIANT_STATE:VideoVariant = {
framerate: 24,
videoPassthrough: false,
videoBitrate: 800,
audioPassthrough: true, // if false, then CAN set audiobitrate
audioBitrate: 0,
encoderPreset: 'veryfast',
};
export const VIDEO_VARIANT_DEFAULTS = {
framerate: {
label: 'Frame rate',
min: 10,
max: 80,
defaultValue: 24,
unit: 'fps',
incrementBy: 1,
tip: 'You prob wont need to touch this unless youre a hardcore gamer and need all the bitties',
},
videoBitrate: {
label: 'Video Bitrate',
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'This is importatnt yo',
},
audioBitrate: {
label: 'Audio Bitrate',
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'nothing to see here'
},
encoderPreset: {
label: 'Encoder Preset',
defaultValue: ENCODER_PRESETS[2],
tip: 'Info and stuff.'
},
videoPassthrough: {
tip: 'If No is selected, then you should set your desired Video Bitrate.'
},
audioPassthrough: {
tip: 'If No is selected, then you should set your desired Audio Bitrate.'
},
};

View File

@ -0,0 +1,257 @@
import React from 'react';
import { Slider, Select, Switch, Divider, Collapse } from 'antd';
import { PRESETS, VideoVariant } from '../../../types/config-section';
import { ENCODER_PRESETS, DEFAULT_VARIANT_STATE } from './constants';
import InfoTip from '../info-tip';
const { Option } = Select;
const { Panel } = Collapse;
const VIDEO_VARIANT_DEFAULTS = {
framerate: {
min: 10,
max: 80,
defaultValue: 24,
unit: 'fps',
incrementBy: 1,
tip: 'You prob wont need to touch this unless youre a hardcore gamer and need all the bitties',
},
videoBitrate: {
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'This is importatnt yo',
},
audioBitrate: {
min: 600,
max: 1200,
defaultValue: 800,
unit: 'kbps',
incrementBy: 100,
tip: 'nothing to see here'
},
encoderPreset: {
defaultValue: ENCODER_PRESETS[2],
tip: 'Info and stuff.'
},
videoPassthrough: {
tip: 'If No is selected, then you should set your desired Video Bitrate.'
},
audioPassthrough: {
tip: 'If No is selected, then you should set your desired Audio Bitrate.'
},
};
interface VideoVariantFormProps {
dataState: VideoVariant;
onUpdateField: () => void;
}
export default function VideoVariantForm({ dataState = DEFAULT_VARIANT_STATE, onUpdateField }: VideoVariantFormProps) {
// const [dataState, setDataState] = useState(initialValues);
const handleFramerateChange = (value: number) => {
onUpdateField({ fieldname: 'framerate', value });
};
const handleVideoBitrateChange = (value: number) => {
onUpdateField({ fieldname: 'videoBitrate', value });
};
const handleAudioBitrateChange = (value: number) => {
onUpdateField({ fieldname: 'audioBitrate', value });
};
const handleEncoderPresetChange = (value: PRESETS) => {
onUpdateField({ fieldname: 'encoderPreset', value });
};
const handleAudioPassChange = (value: boolean) => {
onUpdateField({ fieldname: 'audioPassthrough', value });
};
const handleVideoPassChange = (value: boolean) => {
onUpdateField({ fieldname: 'videoPassthrough', value });
};
const framerateDefaults = VIDEO_VARIANT_DEFAULTS.framerate;
const framerateMin = framerateDefaults.min;
const framerateMax = framerateDefaults.max;
const framerateUnit = framerateDefaults.unit;
const encoderDefaults = VIDEO_VARIANT_DEFAULTS.encoderPreset;
const videoBitrateDefaults = VIDEO_VARIANT_DEFAULTS.videoBitrate;
const videoBRMin = videoBitrateDefaults.min;
const videoBRMax = videoBitrateDefaults.max;
const videoBRUnit = videoBitrateDefaults.unit;
const audioBitrateDefaults = VIDEO_VARIANT_DEFAULTS.audioBitrate;
const audioBRMin = audioBitrateDefaults.min;
const audioBRMax = audioBitrateDefaults.max;
const audioBRUnit = audioBitrateDefaults.unit;
const selectedVideoBRnote = `Selected: ${dataState.videoBitrate}${videoBRUnit} - it sucks`;
const selectedAudioBRnote = `Selected: ${dataState.audioBitrate}${audioBRUnit} - too slow`;
const selectedFramerateNote = `Selected: ${dataState.framerate}${framerateUnit} - whoa there`;
const selectedPresetNote = '';
return (
<div className="variant-form">
<div className="section-intro">
Say a thing here about how this all works.
Read more <a href="https://owncast.online/docs/configuration/">here</a>.
<br /><br />
</div>
{/* ENCODER PRESET FIELD */}
<div className="field">
<p className="label">
<InfoTip tip={encoderDefaults.tip} />
Encoder Preset:
</p>
<div className="form-component">
<Select defaultValue={encoderDefaults.defaultValue} style={{ width: 200 }} onChange={handleEncoderPresetChange}>
{
ENCODER_PRESETS.map(preset => (
<Option
key={`option-${preset}`}
value={preset}
>{preset}</Option>
))
}
</Select>
{selectedPresetNote ? <span className="selected-value-note">{selectedPresetNote}</span> : null }
</div>
</div>
{/* VIDEO PASSTHROUGH FIELD */}
<div style={{ display: 'none'}}>
<div className="field">
<p className="label">
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoPassthrough.tip} />
Use Video Passthrough?
</p>
<div className="form-component">
<Switch
defaultChecked={dataState.videoPassthrough}
onChange={handleVideoPassChange}
checkedChildren="Yes"
unCheckedChildren="No"
/>
</div>
</div>
</div>
{/* VIDEO BITRATE FIELD */}
<div className={`field ${dataState.videoPassthrough ? 'disabled' : ''}`}>
<p className="label">
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.videoBitrate.tip} />
Video Bitrate:
</p>
<div className="form-component">
<Slider
// tooltipVisible={dataState.videoPassthrough !== true}
tipFormatter={value => `${value} ${videoBRUnit}`}
disabled={dataState.videoPassthrough === true}
defaultValue={dataState.videoBitrate}
onChange={handleVideoBitrateChange}
step={videoBitrateDefaults.incrementBy}
min={videoBRMin}
max={videoBRMax}
marks={{
[videoBRMin]: `${videoBRMin} ${videoBRUnit}`,
[videoBRMax]: `${videoBRMax} ${videoBRUnit}`,
}}
/>
{selectedVideoBRnote ? <span className="selected-value-note">{selectedVideoBRnote}</span> : null }
</div>
</div>
<br /><br /><br /><br />
<Collapse>
<Panel header="Advanced Settings" key="1">
<div className="section-intro">
Touch if you dare.
</div>
{/* FRAME RATE FIELD */}
<div className="field">
<p className="label">
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.framerate.tip} />
Frame rate:
</p>
<div className="form-component">
<Slider
// tooltipVisible
tipFormatter={value => `${value} ${framerateUnit}`}
defaultValue={dataState.framerate}
onChange={handleFramerateChange}
step={framerateDefaults.incrementBy}
min={framerateMin}
max={framerateMax}
marks={{
[framerateMin]: `${framerateMin} ${framerateUnit}`,
[framerateMax]: `${framerateMax} ${framerateUnit}`,
}}
/>
{selectedFramerateNote ? <span className="selected-value-note">{selectedFramerateNote}</span> : null }
</div>
</div>
<Divider />
{/* AUDIO PASSTHROUGH FIELD */}
<div className="field">
<p className="label">
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.audioPassthrough.tip} />
Use Audio Passthrough?
</p>
<div className="form-component">
<Switch
defaultChecked={dataState.audioPassthrough}
onChange={handleAudioPassChange}
checkedChildren="Yes"
unCheckedChildren="No"
/>
{dataState.audioPassthrough ? <span className="note">Same as source</span>: null}
</div>
</div>
{/* AUDIO BITRATE FIELD */}
<div className={`field ${dataState.audioPassthrough ? 'disabled' : ''}`}>
<p className="label">
<InfoTip tip={VIDEO_VARIANT_DEFAULTS.audioBitrate.tip} />
Audio Bitrate:
</p>
<div className="form-component">
<Slider
// tooltipVisible={dataState.audioPassthrough !== true}
tipFormatter={value => `${value} ${audioBRUnit}`}
disabled={dataState.audioPassthrough === true}
defaultValue={dataState.audioBitrate}
onChange={handleAudioBitrateChange}
step={audioBitrateDefaults.incrementBy}
min={audioBRMin}
max={audioBRMax}
marks={{
[audioBRMin]: `${audioBRMin} ${audioBRUnit}`,
[audioBRMax]: `${audioBRMax} ${audioBRUnit}`,
}}
/>
{selectedAudioBRnote ? <span className="selected-value-note">{selectedAudioBRnote}</span> : null }
</div>
</div>
</Panel>
</Collapse>
</div>
);
}

View File

@ -1,12 +1,19 @@
import React, { useContext } from 'react';
import { Typography, Table, Modal } from 'antd';
import React, { useContext, useState, useEffect } from 'react';
import { Typography, Table, Modal, Button } from 'antd';
import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { ColumnsType } from 'antd/lib/table';
import { ServerStatusContext } from '../../../utils/server-status-context';
import { VideoVariant } from '../../../types/config-section';
import VideoVariantForm from './video-variant-form';
import { DEFAULT_VARIANT_STATE } from './constants';
const { Title } = Typography;
export default function CurrentVariantsTable() {
const serverStatusData = useContext(ServerStatusContext);
const [displayModal, setDisplayModal] = useState(false);
const [editId, setEditId] = useState(0);
const [dataState, setDataState] = useState(DEFAULT_VARIANT_STATE);
const { serverConfig } = serverStatusData || {};
const { videoSettings } = serverConfig || {};
const { videoQualityVariants } = videoSettings || {};
@ -14,8 +21,23 @@ export default function CurrentVariantsTable() {
return null;
}
const handleModalOk = () => {
setDisplayModal(false);
setEditId(-1);
}
const handleModalCancel = () => {
setDisplayModal(false);
setEditId(-1);
}
const handleUpdateField = (fieldName: string, value: any) => {
setDataState({
...dataState,
[fieldName]: value,
});
}
const videoQualityColumns = [
const videoQualityColumns: ColumnsType<VideoVariant> = [
{
title: "#",
dataIndex: "key",
@ -28,13 +50,13 @@ export default function CurrentVariantsTable() {
render: (bitrate: number) =>
!bitrate ? "Same as source" : `${bitrate} kbps`,
},
{
title: "Framerate",
dataIndex: "framerate",
key: "framerate",
render: (framerate: number) =>
!framerate ? "Same as source" : `${framerate} fps`,
},
// {
// title: "Framerate",
// dataIndex: "framerate",
// key: "framerate",
// render: (framerate: number) =>
// !framerate ? "Same as source" : `${framerate} fps`,
// },
{
title: "Encoder preset",
dataIndex: "encoderPreset",
@ -42,18 +64,37 @@ export default function CurrentVariantsTable() {
render: (preset: string) =>
!preset ? "n/a" : preset,
},
{
title: "Audio bitrate",
dataIndex: "audioBitrate",
key: "audioBitrate",
render: (bitrate: number) =>
!bitrate ? "Same as source" : `${bitrate} kbps`,
},
// {
// title: "Audio bitrate",
// dataIndex: "audioBitrate",
// key: "audioBitrate",
// render: (bitrate: number) =>
// !bitrate ? "Same as source" : `${bitrate} kbps`,
// },
// {
// title: "Audio passthrough",
// dataIndex: "audioPassthrough",
// key: "audioPassthrough",
// render: (item: boolean) => item ? <CheckOutlined />: <CloseOutlined />,
// },
// {
// title: "Video passthrough",
// dataIndex: "videoPassthrough",
// key: "audioPassthrough",
// render: (item: boolean) => item ? <CheckOutlined />: <CloseOutlined />,
// },
{
title: '',
dataIndex: '',
key: 'edit',
render: () => "edit.. populate modal",
render: (data: VideoVariant) => (
<Button type="primary" size="small" onClick={() =>{
setEditId(data.key - 1);
setDisplayModal(true);
}}>
Edit
</Button>
),
},
];
@ -71,6 +112,19 @@ export default function CurrentVariantsTable() {
dataSource={videoQualityVariantData}
/>
<Modal
title="Edit Video Variant Details"
visible={displayModal}
onOk={handleModalOk}
onCancel={handleModalCancel}
// confirmLoading={confirmLoading}
>
<VideoVariantForm initialValues={{...videoQualityVariants[editId]}} onUpdateField={handleUpdateField} />
</Modal>
</>
);
}

View File

@ -44,10 +44,6 @@ export default function VideoConfig() {
<Title level={5}>Learn more about configuring Owncast <a href="https://owncast.online/docs/configuration">by visiting the documentation.</a></Title>
<div className="config-video-misc">
<Slider defaultValue={37} />
<Slider tooltipVisible step={10} defaultValue={37} />
<Form
form={form}
layout="vertical"
@ -56,6 +52,7 @@ export default function VideoConfig() {
<TextField fieldName="segmentLengthSeconds" type={TEXTFIELD_TYPE_NUMBER} {...extraProps} />
</Form>
</div>
<VideoVariantsTable />
</div>
);

View File

@ -186,4 +186,72 @@
// align-items: flex-start;
// }
}
}
.variant-form {
.blurb {
margin: 1em;
opacity: .75;
}
.note {
display: inline-block;
margin-left: 1em;
font-size: .75em;
opacity: .5;
font-style: italic;
}
.section-intro {
margin-bottom: 2em;
}
.field {
margin-bottom: 2em;
display: flex;
flex-direction: row;
justify-content: center;
align-items: flex-start;
transform: opacity .15s;
&.disabled {
opacity: .25;
}
.label {
width: 40%;
text-align: right;
padding-right: 2em;
font-weight: bold;
color: var(--owncast-purple);
}
.info-tip {
margin-right: 1em;
}
.form-component {
width: 60%;
.ant-slider-with-marks {
margin-right: 2em;
}
.ant-slider-mark-text {
font-size: .85em;
white-space: nowrap;
}
.selected-value-note {
font-size: .85em;
display: inline-block;
text-align: center;
}
}
}
.ant-collapse {
border: none;
border-radius: 6px;
}
.ant-collapse > .ant-collapse-item:last-child,
.ant-collapse > .ant-collapse-item:last-child > .ant-collapse-header {
border: none;
background-color: rgba(0,0,0,.25);
border-radius: 6px;
}
.ant-collapse-content {
background-color: rgba(0,0,0,.1);
}
}

View File

@ -27,12 +27,9 @@ pre {
display: block;
padding: 1rem;
margin: .5rem 0;
background-color: #eee;
background-color: rgb(44, 44, 44);
color:lightgrey;
}
// pre {
// background-color: rgb(44, 44, 44);
// color:lightgrey;
// }
code {
color: var(--owncast-purple);
@ -41,12 +38,6 @@ code {
.owncast-layout .ant-menu-dark.ant-menu-dark:not(.ant-menu-horizontal) .ant-menu-item-selected {
background-color: $owncast-purple;
}
// 07050d
// 020103
// GENERAL ANT FORM OVERRIDES
.ant-layout,
.ant-layout-footer,
@ -93,6 +84,17 @@ code {
.ant-table-small .ant-table-thead > tr > th {
background-color: #000;
}
.ant-modal-content {
border-radius: 6px;
}
.ant-modal-header {
background-color: #1c173d;
border-radius: 6px 6px 0 0;
}
.ant-modal-title {
font-weight: bold;
font-size: 1.5em;
}
// markdown editor overrides

View File

@ -49,13 +49,18 @@ export interface ConfigInstanceDetailsFields {
title: string;
}
export type PRESETS = 'fast' | 'faster' | 'veryfast' | 'superfast' | 'ultrafast';
export interface VideoVariant {
audioBitrate: number;
audioPassthrough: false | number;
encoderPreset: 'ultrafast' | 'superfast' | 'veryfast' | 'faster' | 'fast';
key?: number; // unique identifier generated on client side just for ant table rendering
encoderPreset: PRESETS,
framerate: number;
videoBitrate: number;
audioPassthrough: boolean;
audioBitrate: number;
videoPassthrough: boolean;
videoBitrate: number;
}
export interface VideoSettingsFields {
numberOfPlaylistItems: number;

View File

@ -3,6 +3,7 @@ import PropTypes, { any } from 'prop-types';
import { STATUS, fetchData, FETCH_INTERVAL, SERVER_CONFIG } from './apis';
import { ConfigDetails, UpdateArgs } from '../types/config-section';
import { DEFAULT_VARIANT_STATE } from '../pages/components/config/constants';
export const initialServerConfigState: ConfigDetails = {
streamKey: '',
@ -27,16 +28,7 @@ export const initialServerConfigState: ConfigDetails = {
videoSettings: {
numberOfPlaylistItems: 5,
segmentLengthSeconds: 4,
videoQualityVariants: [
{
audioPassthrough: false,
videoPassthrough: false,
videoBitrate: 0,
audioBitrate: 0,
framerate: 0,
encoderPreset: 'veryfast',
},
],
videoQualityVariants: [DEFAULT_VARIANT_STATE],
}
};