diff --git a/web2/src/helpers/util.ts b/web2/src/helpers/util.ts
index 07ada84f2..901046e63 100644
--- a/web2/src/helpers/util.ts
+++ b/web2/src/helpers/util.ts
@@ -50,3 +50,7 @@ export const fromStringResolution = (
const [h, w] = res.split('x', 1);
return { widthPx: parseInt(w), heightPx: parseInt(h) };
};
+
+export const hasOnlyDigits = (value: string) => {
+ return /^-?\d+$/.test(value);
+};
diff --git a/web2/src/pages/settings/FfmpegSettingsPage.tsx b/web2/src/pages/settings/FfmpegSettingsPage.tsx
index f1b7f7b41..19c1385be 100644
--- a/web2/src/pages/settings/FfmpegSettingsPage.tsx
+++ b/web2/src/pages/settings/FfmpegSettingsPage.tsx
@@ -1,3 +1,4 @@
+import React, { useEffect } from 'react';
import {
Button,
Checkbox,
@@ -6,21 +7,29 @@ import {
FormControlLabel,
InputLabel,
MenuItem,
- Paper,
Stack,
Select,
TextField,
Typography,
+ SelectChangeEvent,
+ Alert,
} from '@mui/material';
import { useFfmpegSettings } from '../../hooks/settingsHooks.ts';
+import { hasOnlyDigits } from '../../helpers/util.ts';
+
+const supportedVideoBuffer = [
+ { value: 0, string: '0 Seconds' },
+ { value: 1000, string: '1 Second' },
+ { value: 2000, string: '2 Seconds' },
+ { value: 3000, string: '3 Seconds' },
+ { value: 4000, string: '4 Seconds' },
+ { value: 5000, string: '5 Seconds' },
+ { value: 10000, string: '10 Seconds' },
+];
export default function FfmpegSettingsPage() {
const { data, isPending, error } = useFfmpegSettings();
- if (isPending || error) {
- return
;
- }
-
const defaultFFMPEGSettings = {
ffmpegExecutablePath: '/usr/bin/ffmpeg',
numThreads: 4,
@@ -29,68 +38,160 @@ export default function FfmpegSettingsPage() {
enableTranscoding: false,
};
+ const [ffmpegExecutablePath, setFfmpegExecutablePath] =
+ React.useState(defaultFFMPEGSettings.ffmpegExecutablePath);
+
+ const [numThreads, setNumThreads] = React.useState(
+ defaultFFMPEGSettings.numThreads.toString(),
+ );
+
+ const [enableLogging, setEnableLogging] = React.useState(
+ defaultFFMPEGSettings.enableLogging,
+ );
+
+ const [videoBufferSize, setVideoBufferSize] = React.useState(
+ defaultFFMPEGSettings.videoBufferSize.toString(),
+ );
+
+ const [enableTranscoding, setEnableTranscoding] = React.useState(
+ defaultFFMPEGSettings.enableTranscoding,
+ );
+
+ const [showFormError, setShowFormError] = React.useState(false);
+
+ useEffect(() => {
+ setFfmpegExecutablePath(
+ data?.ffmpegExecutablePath || defaultFFMPEGSettings.ffmpegExecutablePath,
+ );
+
+ setNumThreads(
+ data?.numThreads.toString() ||
+ defaultFFMPEGSettings.numThreads.toString(),
+ );
+
+ setEnableLogging(
+ data?.enableLogging || defaultFFMPEGSettings.enableLogging,
+ );
+
+ setVideoBufferSize(
+ data?.videoBufferSize.toString() ||
+ defaultFFMPEGSettings.videoBufferSize.toString(),
+ );
+
+ setEnableTranscoding(
+ data?.enableTranscoding || defaultFFMPEGSettings.enableTranscoding,
+ );
+ }, [data]);
+
+ const handleFfmpegExecutablePath = (
+ event: React.ChangeEvent,
+ ) => {
+ setFfmpegExecutablePath(event.target.value);
+ };
+
+ const handleNumThreads = (event: React.ChangeEvent) => {
+ setNumThreads(event.target.value);
+ };
+
+ const handleEnableLogging = () => {
+ setEnableLogging(!enableLogging);
+ };
+
+ const handleVideoBufferSize = (event: SelectChangeEvent) => {
+ setVideoBufferSize(event.target.value);
+ };
+
+ const handleEnableTranscoding = () => {
+ setEnableTranscoding(!enableTranscoding);
+ };
+
+ const handleValidateFields = (event: React.FocusEvent) => {
+ setShowFormError(!hasOnlyDigits(event.target.value));
+ };
+
+ if (isPending || error) {
+ return ;
+ }
+
return (
<>
-
-
-
-
-
- Miscellaneous Options
-
-
-
- }
- label="Log FFMPEG to console"
- />
-
-
- Video Buffer
-
-
- Note: If you experience playback issues upon stream start, try
- increasing this.
-
-
-
- Transcoding Features
-
-
- }
- label="Enable FFMPEG Transcoding"
- />
-
- Transcoding is required for some features like channel overlay and
- measures to prevent issues when switching episodes. The trade-off is
- quality loss and additional computing resource requirements.
-
-
-
+
+
+
+
+ Miscellaneous Options
+
+ {showFormError && (
+
+ Invalid input. Please make sure number of threads is a number
+
+ )}
+
+
+
+ }
+ label="Log FFMPEG to console"
+ />
+
+
+ Video Buffer
+
+
+ Note: If you experience playback issues upon stream start, try
+ increasing this.
+
+
+
+ Transcoding Features
+
+
+
+ }
+ label="Enable FFMPEG Transcoding"
+ />
+
+ Transcoding is required for some features like channel overlay and
+ measures to prevent issues when switching episodes. The trade-off is
+ quality loss and additional computing resource requirements.
+
+
-
+
>
);
diff --git a/web2/src/pages/settings/GeneralSettingsPage.tsx b/web2/src/pages/settings/GeneralSettingsPage.tsx
index cc81a091c..bf55c217b 100644
--- a/web2/src/pages/settings/GeneralSettingsPage.tsx
+++ b/web2/src/pages/settings/GeneralSettingsPage.tsx
@@ -1,9 +1,8 @@
-import { Button, Paper, Stack } from '@mui/material';
+import { Button, Stack } from '@mui/material';
export default function GeneralSettingsPage() {
return (
<>
-
diff --git a/web2/src/pages/settings/HdhrSettingsPage.tsx b/web2/src/pages/settings/HdhrSettingsPage.tsx
index 38c62152a..923ad04be 100644
--- a/web2/src/pages/settings/HdhrSettingsPage.tsx
+++ b/web2/src/pages/settings/HdhrSettingsPage.tsx
@@ -1,12 +1,9 @@
import {
- Box,
Button,
Checkbox,
FormControl,
FormLabel,
- FormControlLabel,
FormHelperText,
- Paper,
Stack,
TextField,
} from '@mui/material';
@@ -28,22 +25,20 @@ export default function HdhrSettingsPage() {
return (
<>
-
-
-
- Enable SSDP server
- * Restart required
-
-
-
+
+
+ Enable SSDP server
+ * Restart required
+
+
diff --git a/web2/src/pages/settings/PlexSettingsPage.tsx b/web2/src/pages/settings/PlexSettingsPage.tsx
index c6bdde847..b7eae9b27 100644
--- a/web2/src/pages/settings/PlexSettingsPage.tsx
+++ b/web2/src/pages/settings/PlexSettingsPage.tsx
@@ -27,6 +27,7 @@ import {
Typography,
Input,
InputAdornment,
+ SelectChangeEvent,
} from '@mui/material';
import { AddCircle, Close, Done, Edit } from '@mui/icons-material';
import { useMutation, useQueryClient } from '@tanstack/react-query';
@@ -49,10 +50,52 @@ const supportedResolutions = [
'3840x2160',
];
+const supportedAudioChannels = [
+ '1.0',
+ '2.0',
+ '2.1',
+ '4.0',
+ '5.0',
+ '5.1',
+ '6.1',
+ '7.1',
+];
+
+const supportedAudioBoost = [
+ { value: 100, string: '0 Seconds' },
+ { value: 120, string: '1 Second' },
+ { value: 140, string: '2 Seconds' },
+ { value: 160, string: '3 Seconds' },
+ { value: 180, string: '4 Seconds' },
+];
+
const defaultPlexSettings = {
- maxPlayableResolution: '1920x1080',
- showSubtitles: false,
+ audioBoost: 100,
+ audioCodecs: ['ac3'],
+ directStreamBitrate: 20000,
+ enableDebugLogging: false,
+ enableSubtitles: false,
+ forceDirectPlay: false,
+ maxAudioChannels: '2.0',
+ maxPlayableResolution: {
+ widthPx: 1920,
+ heightPx: 1080,
+ },
+ maxTranscodeResolution: {
+ widthPx: 1920,
+ heightPx: 1080,
+ },
+ mediaBufferSize: 1000,
+ pathReplace: '',
+ pathReplaceWith: '',
+ streamPath: 'plex',
+ streamProtocol: 'http',
+ subtitleSize: 100,
+ transcodeBitrate: 2000,
+ transcodeMediaBufferSize: 20000,
+ updatePlayStatus: false,
videoCodecs: ['h264', 'hevc', 'mpeg2video', 'av1'],
+ showSubtitles: false,
};
export default function PlexSettingsPage() {
@@ -75,27 +118,74 @@ export default function PlexSettingsPage() {
);
const [videoCodecs, setVideoCodecs] = React.useState(
- streamSettings?.videoCodecs || defaultPlexSettings.videoCodecs,
+ defaultPlexSettings.videoCodecs,
);
- const [maxPlayableResolution, setMaxPlayableResolution] = useState(
- defaultPlexSettings.maxPlayableResolution,
+ const [addVideoCodecs, setAddVideoCodecs] = React.useState('');
+
+ const [audioCodecs, setAudioCodecs] = React.useState(
+ defaultPlexSettings.audioCodecs,
);
- const [addVideoCodecs, setAddVideoCodecs] = React.useState('');
+ const [addAudioCodecs, setAddAudioCodecs] = React.useState('');
+
+ const [maxAudioChannels, setMaxAudioChannels] = React.useState(
+ defaultPlexSettings.maxAudioChannels,
+ );
+
+ const [directStreamBitrate, setDirectStreamBitrate] =
+ React.useState('');
+
+ const [transcodeBitrate, setTranscodeBitrate] = React.useState('');
+
+ const [mediaBufferSize, setMediaBufferSize] = React.useState('');
+
+ const [transcodeMediaBufferSize, setTranscodeMediaBufferSize] =
+ React.useState('');
useEffect(() => {
- setVideoCodecs(streamSettings?.videoCodecs || []);
+ setVideoCodecs(
+ streamSettings?.videoCodecs || defaultPlexSettings.videoCodecs,
+ );
setMaxPlayableResolution(
toStringResolution(
- streamSettings?.maxPlayableResolution || {
- widthPx: 1920,
- heightPx: 1080,
- },
+ streamSettings?.maxPlayableResolution ||
+ defaultPlexSettings.maxPlayableResolution,
),
);
- }, [streamSettings?.maxPlayableResolution, streamSettings?.videoCodecs]);
+
+ setMaxDirectStreamBitrate(
+ streamSettings?.directStreamBitrate.toString() ||
+ defaultPlexSettings.directStreamBitrate.toString(),
+ );
+
+ setAudioCodecs(
+ streamSettings?.audioCodecs || defaultPlexSettings.audioCodecs,
+ );
+
+ setMaxAudioChannels(
+ streamSettings?.maxAudioChannels || defaultPlexSettings.maxAudioChannels,
+ );
+
+ setDirectStreamBitrate(
+ streamSettings?.directStreamBitrate.toString() ||
+ defaultPlexSettings.directStreamBitrate.toString(),
+ );
+
+ setTranscodeBitrate(
+ streamSettings?.transcodeBitrate.toString() ||
+ defaultPlexSettings.transcodeBitrate.toString(),
+ );
+ setMediaBufferSize(
+ streamSettings?.mediaBufferSize.toString() ||
+ defaultPlexSettings.mediaBufferSize.toString(),
+ );
+ setTranscodeMediaBufferSize(
+ streamSettings?.transcodeMediaBufferSize.toString() ||
+ defaultPlexSettings.transcodeMediaBufferSize.toString(),
+ );
+ }, [streamSettings]);
const handleVideoCodecUpdate = () => {
if (!addVideoCodecs.length) {
@@ -119,6 +209,50 @@ export default function PlexSettingsPage() {
setAddVideoCodecs(newVideoCodecs);
};
+ const handleAudioCodecUpdate = () => {
+ if (!addAudioCodecs.length) {
+ return;
+ }
+
+ // If there is a comma or white space at the end of user input, trim it
+ let newAudioCodecs: string[] = [addAudioCodecs.replace(/,\s*$/, '')];
+
+ if (addAudioCodecs?.indexOf(',') > -1) {
+ newAudioCodecs = newAudioCodecs[0].split(',');
+ } else {
+ newAudioCodecs = [newAudioCodecs[0]];
+ }
+
+ setAudioCodecs([...audioCodecs, ...newAudioCodecs]);
+ setAddAudioCodecs('');
+ };
+
+ const handleAudioCodecChange = (newAudioCodecs: string) => {
+ setAddAudioCodecs(newAudioCodecs);
+ };
+
+ const [maxPlayableResolution, setMaxPlayableResolution] = useState(
+ toStringResolution(defaultPlexSettings.maxPlayableResolution),
+ );
+
+ const handleMaxPlayableResolution = (event: SelectChangeEvent) => {
+ setMaxPlayableResolution(event.target.value as string);
+ };
+
+ const [maxDirectStreamBitrate, setMaxDirectStreamBitrate] = useState(
+ defaultPlexSettings.directStreamBitrate.toString(),
+ );
+
+ const handleMaxDirectStreamBitrate = (
+ event: React.ChangeEvent,
+ ) => {
+ setMaxDirectStreamBitrate(event.target.value);
+ };
+
+ const handleMaxAudioChannels = (event: SelectChangeEvent) => {
+ setMaxAudioChannels(event.target.value as string);
+ };
+
const onSubtitleChange = () => {
setShowSubtitles(!showSubtitles);
};
@@ -146,8 +280,10 @@ export default function PlexSettingsPage() {
);
};
- const removeAudioCodec = (codec: Text) => {
- console.log(codec); // TODO
+ const removeAudioCodec = (codecToDelete: string) => () => {
+ setAudioCodecs(
+ (codecs) => codecs?.filter((codec) => codec !== codecToDelete),
+ );
};
const UIRouteSuccess = true; // TODO
@@ -222,7 +358,7 @@ export default function PlexSettingsPage() {
const renderServersTable = () => {
return (
-
+
@@ -314,6 +450,7 @@ export default function PlexSettingsPage() {
id="max-playable-resolution"
label="Max Playable Resolution"
value={maxPlayableResolution}
+ onChange={handleMaxPlayableResolution}
>
{supportedResolutions.map((res) => (