From 8458849d885dfa15481c109dce1e84ba5f4cec92 Mon Sep 17 00:00:00 2001 From: gingervitis Date: Sun, 10 Jan 2021 02:37:22 -0800 Subject: [PATCH] form fields for video config modal --- web/pages/components/config/constants.tsx | 59 +++- .../components/config/video-variant-form.tsx | 257 ++++++++++++++++++ .../config/video-variants-table.tsx | 92 +++++-- web/pages/config-video.tsx | 5 +- web/styles/config.scss | 68 +++++ web/styles/globals.scss | 24 +- web/types/config-section.ts | 13 +- web/utils/server-status-context.tsx | 12 +- 8 files changed, 481 insertions(+), 49 deletions(-) create mode 100644 web/pages/components/config/video-variant-form.tsx diff --git a/web/pages/components/config/constants.tsx b/web/pages/components/config/constants.tsx index e3748a33d..196740a04 100644 --- a/web/pages/components/config/constants.tsx +++ b/web/pages/components/config/constants.tsx @@ -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.' + }, +}; diff --git a/web/pages/components/config/video-variant-form.tsx b/web/pages/components/config/video-variant-form.tsx new file mode 100644 index 000000000..a4262f8ee --- /dev/null +++ b/web/pages/components/config/video-variant-form.tsx @@ -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 ( +
+
+ Say a thing here about how this all works. + + Read more here. +

+
+ + {/* ENCODER PRESET FIELD */} +
+

+ + Encoder Preset: +

+
+ + {selectedPresetNote ? {selectedPresetNote} : null } +
+
+ + + + {/* VIDEO PASSTHROUGH FIELD */} +
+
+

+ + Use Video Passthrough? +

+
+ +
+
+
+ {/* VIDEO BITRATE FIELD */} +
+

+ + Video Bitrate: +

+
+ `${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 ? {selectedVideoBRnote} : null } + +
+
+ + +



+ + + +
+ Touch if you dare. +
+ + {/* FRAME RATE FIELD */} +
+

+ + Frame rate: +

+
+ `${value} ${framerateUnit}`} + defaultValue={dataState.framerate} + onChange={handleFramerateChange} + step={framerateDefaults.incrementBy} + min={framerateMin} + max={framerateMax} + marks={{ + [framerateMin]: `${framerateMin} ${framerateUnit}`, + [framerateMax]: `${framerateMax} ${framerateUnit}`, + }} + /> + {selectedFramerateNote ? {selectedFramerateNote} : null } + +
+
+ + + + {/* AUDIO PASSTHROUGH FIELD */} +
+

+ + Use Audio Passthrough? +

+
+ + {dataState.audioPassthrough ? Same as source: null} +
+
+ + {/* AUDIO BITRATE FIELD */} +
+

+ + Audio Bitrate: +

+
+ `${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 ? {selectedAudioBRnote} : null } +
+
+ + +
+
+ +
+ ); + +} \ No newline at end of file diff --git a/web/pages/components/config/video-variants-table.tsx b/web/pages/components/config/video-variants-table.tsx index a46516862..3c38377f3 100644 --- a/web/pages/components/config/video-variants-table.tsx +++ b/web/pages/components/config/video-variants-table.tsx @@ -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 = [ { 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 ? : , + // }, + // { + // title: "Video passthrough", + // dataIndex: "videoPassthrough", + // key: "audioPassthrough", + // render: (item: boolean) => item ? : , + // }, { title: '', dataIndex: '', key: 'edit', - render: () => "edit.. populate modal", + render: (data: VideoVariant) => ( + + ), }, ]; @@ -71,6 +112,19 @@ export default function CurrentVariantsTable() { dataSource={videoQualityVariantData} /> + + + + + + + ); } \ No newline at end of file diff --git a/web/pages/config-video.tsx b/web/pages/config-video.tsx index 539b83675..f190dccf3 100644 --- a/web/pages/config-video.tsx +++ b/web/pages/config-video.tsx @@ -44,10 +44,6 @@ export default function VideoConfig() { Learn more about configuring Owncast <a href="https://owncast.online/docs/configuration">by visiting the documentation.</a>
- - - -
+ ); diff --git a/web/styles/config.scss b/web/styles/config.scss index 40c6005ce..50bbed418 100644 --- a/web/styles/config.scss +++ b/web/styles/config.scss @@ -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); + } } diff --git a/web/styles/globals.scss b/web/styles/globals.scss index 643407a43..3c0c68db3 100644 --- a/web/styles/globals.scss +++ b/web/styles/globals.scss @@ -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 diff --git a/web/types/config-section.ts b/web/types/config-section.ts index e04c411da..d533b33fd 100644 --- a/web/types/config-section.ts +++ b/web/types/config-section.ts @@ -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; diff --git a/web/utils/server-status-context.tsx b/web/utils/server-status-context.tsx index e39e65e77..1b0ef152c 100644 --- a/web/utils/server-status-context.tsx +++ b/web/utils/server-status-context.tsx @@ -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], } };