@ -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 } ${ videoBRU nit} ` }
tipFormatter = { value = > ` ${ value } ${ VIDEO_BITRATE_DEFAULTS . u nit} ` }
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 = { videoBRM in}
min = { VIDEO_BITRATE_DEFAULTS . m in}
max = { videoBRM ax}
max = { VIDEO_BITRATE_DEFAULTS . m ax}
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 } ${ framerateU nit} ` }
tipFormatter = { value = > ` ${ value } ${ FRAMERATE_DEFAULTS . u nit} ` }
defaultValue = { dataState . framerate }
defaultValue = { dataState . framerate }
value = { dataState . framerate }
value = { dataState . framerate }
onChange = { handleFramerateChange }
onChange = { handleFramerateChange }
step = { framerateDefaults . incrementBy }
step = { FRAMERATE_DEFAULTS . incrementBy }
min = { framerateM in}
min = { FRAMERATE_DEFAULTS . m in}
max = { framerateM ax}
max = { FRAMERATE_DEFAULTS . m ax}
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 >