123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- // 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, { FC } from 'react';
- import { Popconfirm, Row, Col, Slider, Collapse, Typography, Alert, Button } from 'antd';
- import classNames from 'classnames';
- import dynamic from 'next/dynamic';
- import { FieldUpdaterFunc, VideoVariant, UpdateArgs } from '../../types/config-section';
- import { TextField } from './TextField';
- import {
- DEFAULT_VARIANT_STATE,
- VIDEO_VARIANT_SETTING_DEFAULTS,
- VIDEO_NAME_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 './ToggleSwitch';
- const { Panel } = Collapse;
- // Lazy loaded components
- const ExclamationCircleFilled = dynamic(() => import('@ant-design/icons/ExclamationCircleFilled'), {
- ssr: false,
- });
- export type VideoVariantFormProps = {
- dataState: VideoVariant;
- onUpdateField: FieldUpdaterFunc;
- };
- export const VideoVariantForm: FC<VideoVariantFormProps> = ({
- dataState = DEFAULT_VARIANT_STATE,
- onUpdateField,
- }) => {
- const videoPassthroughEnabled = dataState.videoPassthrough;
- const handleFramerateChange = (value: number) => {
- onUpdateField({ fieldName: 'framerate', value });
- };
- const handleVideoBitrateChange = (value: number) => {
- onUpdateField({ fieldName: 'videoBitrate', value });
- };
- const handleVideoCpuUsageLevelChange = (value: number) => {
- onUpdateField({ fieldName: 'cpuUsageLevel', value });
- };
- const handleScaledWidthChanged = (args: UpdateArgs) => {
- const value = Number(args.value);
- // eslint-disable-next-line no-restricted-globals
- if (isNaN(value)) {
- return;
- }
- onUpdateField({ fieldName: 'scaledWidth', value: value || 0 });
- };
- const handleScaledHeightChanged = (args: UpdateArgs) => {
- const value = Number(args.value);
- // eslint-disable-next-line no-restricted-globals
- if (isNaN(value)) {
- return;
- }
- onUpdateField({ fieldName: 'scaledHeight', value: value || 0 });
- };
- // Video passthrough handling
- const handleVideoPassConfirm = () => {
- onUpdateField({ fieldName: 'videoPassthrough', value: true });
- };
- // If passthrough is currently on, set it back to false on toggle.
- // Else let the Popconfirm turn it on.
- const handleVideoPassthroughToggle = (value: boolean) => {
- if (videoPassthroughEnabled) {
- onUpdateField({ fieldName: 'videoPassthrough', value });
- }
- };
- const handleNameChanged = (args: UpdateArgs) => {
- onUpdateField({ fieldName: 'name', value: args.value });
- };
- // Slider notes
- const selectedVideoBRnote = () => {
- if (videoPassthroughEnabled) {
- return 'Bitrate selection is disabled when Video Passthrough is enabled.';
- }
- let note = `${dataState.videoBitrate}${VIDEO_BITRATE_DEFAULTS.unit}`;
- if (dataState.videoBitrate < 2000) {
- note = `${note} - Good for low bandwidth environments.`;
- } else if (dataState.videoBitrate < 3500) {
- note = `${note} - Good for most bandwidth environments.`;
- } else {
- note = `${note} - Good for high bandwidth environments.`;
- }
- return note;
- };
- const selectedFramerateNote = () => {
- if (videoPassthroughEnabled) {
- return 'Framerate selection is disabled when Video Passthrough is enabled.';
- }
- return FRAMERATE_TOOLTIPS[dataState.framerate] || '';
- };
- const cpuUsageNote = () => {
- if (videoPassthroughEnabled) {
- return 'CPU usage selection is disabled when Video Passthrough is enabled.';
- }
- return ENCODER_PRESET_TOOLTIPS[dataState.cpuUsageLevel] || '';
- };
- const classes = classNames({
- 'config-variant-form': true,
- 'video-passthrough-enabled': videoPassthroughEnabled,
- });
- return (
- <div className={classes}>
- <div className="video-varient-alert">
- <Alert
- type="info"
- action={
- <a
- href="https://owncast.online/docs/video?source=admin"
- target="_blank"
- rel="noopener noreferrer"
- >
- <div className="video-varient-alert-button-container">
- <Button size="small" type="text" icon={<ExclamationCircleFilled />}>
- Read more about how each of these settings can impact the performance of your
- server.
- </Button>
- </div>
- </a>
- }
- />
- </div>
- {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}>
- <Col xs={24} lg={{ span: 24, pull: 3 }} className="video-text-field-container">
- <TextField
- maxLength="10"
- {...VIDEO_NAME_DEFAULTS}
- value={dataState.name}
- onChange={handleNameChanged}
- />
- </Col>
- <Col sm={24} md={12}>
- <div className="form-module cpu-usage-container">
- <Typography.Title level={3}>CPU or GPU Utilization</Typography.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 => ENCODER_PRESET_TOOLTIPS[value]}
- onChange={handleVideoCpuUsageLevelChange}
- min={0}
- max={Object.keys(ENCODER_PRESET_SLIDER_MARKS).length - 1}
- 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">
- This could mean GPU or CPU usage depending on your server environment.
- <br />
- <br />
- <a
- href="https://owncast.online/docs/video/?source=admin#cpu-usage"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read more about hardware performance.
- </a>
- </p>
- </div>
- </Col>
- <Col sm={24} md={11} offset={1}>
- {/* VIDEO BITRATE FIELD */}
- <div
- className={`form-module bitrate-container ${
- dataState.videoPassthrough ? 'disabled' : ''
- }`}
- >
- <Typography.Title level={3}>Video Bitrate</Typography.Title>
- <p className="description">{VIDEO_BITRATE_DEFAULTS.tip}</p>
- <div className="segment-slider-container">
- <Slider
- tipFormatter={value => `${value} ${VIDEO_BITRATE_DEFAULTS.unit}`}
- disabled={dataState.videoPassthrough}
- defaultValue={dataState.videoBitrate}
- value={dataState.videoBitrate}
- onChange={handleVideoBitrateChange}
- step={VIDEO_BITRATE_DEFAULTS.incrementBy}
- min={VIDEO_BITRATE_DEFAULTS.min}
- max={VIDEO_BITRATE_DEFAULTS.max}
- marks={VIDEO_BITRATE_SLIDER_MARKS}
- />
- <p className="selected-value-note">{selectedVideoBRnote()}</p>
- </div>
- <p className="read-more-subtext">
- <a
- href="https://owncast.online/docs/video/?source=admin"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read more about bitrates.
- </a>
- </p>
- </div>
- </Col>
- </Row>
- <Collapse className="advanced-settings">
- <Panel header="Advanced Settings" key="1">
- <Row gutter={16}>
- <Col sm={24} md={12}>
- <div className="form-module resolution-module">
- <Typography.Title level={3}>Resolution</Typography.Title>
- <p className="description">
- Resizing your content will take additional resources on your server. If you wish
- to optionally resize your content for this stream output then you should either
- set the width <strong>or</strong> the height to keep your aspect ratio. <br />
- <br />
- <a
- href="https://owncast.online/docs/video/?source=admin"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read more about resolutions.
- </a>
- </p>
- <br />
- <TextField
- type="number"
- {...VIDEO_VARIANT_SETTING_DEFAULTS.scaledWidth}
- value={dataState.scaledWidth}
- onChange={handleScaledWidthChanged}
- disabled={dataState.videoPassthrough}
- />
- <TextField
- type="number"
- {...VIDEO_VARIANT_SETTING_DEFAULTS.scaledHeight}
- value={dataState.scaledHeight}
- onChange={handleScaledHeightChanged}
- disabled={dataState.videoPassthrough}
- />
- </div>
- </Col>
- <Col sm={24} md={12}>
- {/* VIDEO PASSTHROUGH FIELD */}
- <div className="form-module video-passthrough-module">
- <Typography.Title level={3}>Video Passthrough</Typography.Title>
- <div className="description">
- <p>
- Enabling video passthrough may allow for less hardware utilization, but may also
- make your stream <strong>unplayable</strong>.
- </p>
- <p>
- All other settings for this stream output will be disabled if passthrough is
- used.
- </p>
- <p>
- <a
- href="https://owncast.online/docs/video/?source=admin#video-passthrough"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read the documentation before enabling, as it impacts your stream.
- </a>
- </p>
- </div>
- <div className="advanced-switch-container">
- <Popconfirm
- disabled={dataState.videoPassthrough === true}
- title="Did you read the documentation about video passthrough and understand the risks involved with enabling it?"
- icon={<ExclamationCircleFilled />}
- onConfirm={handleVideoPassConfirm}
- okText="Yes"
- cancelText="No"
- getPopupContainer={triggerNode => triggerNode}
- placement="topLeft"
- >
- {/* adding an <a> tag to force Popcofirm to register click on toggle */}
- {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */}
- <a href="#">
- <div className="advanced-description-switch-container">
- <div className="advanced-description-wrapper">
- <p>Use Video Passthrough?</p>
- </div>
- <ToggleSwitch
- label=""
- fieldName="video-passthrough"
- checked={dataState.videoPassthrough}
- onChange={handleVideoPassthroughToggle}
- />
- </div>
- </a>
- <p>*{VIDEO_VARIANT_SETTING_DEFAULTS.videoPassthrough.tip}</p>
- </Popconfirm>
- </div>
- </div>
- </Col>
- </Row>
- {/* FRAME RATE FIELD */}
- <div className="form-module frame-rate-module">
- <Typography.Title level={3}>Frame rate</Typography.Title>
- <p className="description">{FRAMERATE_DEFAULTS.tip}</p>
- <div className="segment-slider-container">
- <Slider
- tipFormatter={value => `${value} ${FRAMERATE_DEFAULTS.unit}`}
- defaultValue={dataState.framerate}
- value={dataState.framerate}
- onChange={handleFramerateChange}
- step={FRAMERATE_DEFAULTS.incrementBy}
- min={FRAMERATE_DEFAULTS.min}
- max={FRAMERATE_DEFAULTS.max}
- marks={FRAMERATE_SLIDER_MARKS}
- disabled={dataState.videoPassthrough}
- />
- <p className="selected-value-note">{selectedFramerateNote()}</p>
- </div>
- <p className="read-more-subtext">
- <a
- href="https://owncast.online/docs/video/?source=admin#framerate"
- target="_blank"
- rel="noopener noreferrer"
- >
- Read more about framerates.
- </a>
- </p>
- </div>
- </Panel>
- </Collapse>
- </div>
- );
- };
|