feat: support customising which streams to proxy, fix duplication check bypasses by setting falsy/false values to undefined to match intial state, refactor multi select component usage

closes #8
closes #17
closes #18
This commit is contained in:
Viren070
2025-01-17 15:05:11 +00:00
parent 20a9ecee98
commit dcb0353f92
7 changed files with 211 additions and 120 deletions
+27 -4
View File
@@ -434,6 +434,31 @@ export class AIOStreams {
};
}
private shouldProxyStream(stream: ParsedStream): boolean {
const mediaFlowConfig = getMediaFlowConfig(this.config);
if (!mediaFlowConfig.mediaFlowEnabled) return false;
if (!stream.url) return false;
// now check if mediaFlowConfig.proxiedAddons or mediaFlowConfig.proxiedServices is not null
if (
mediaFlowConfig.proxiedAddons &&
!mediaFlowConfig.proxiedAddons.includes(stream.addon.id)
)
return false;
if (
(mediaFlowConfig.proxiedServices &&
stream.provider &&
!mediaFlowConfig.proxiedServices.includes(stream.provider.id)) ||
(mediaFlowConfig.proxiedServices &&
!stream.provider &&
!mediaFlowConfig.proxiedServices.includes('none'))
)
return false;
return true;
}
private async createStreamObject(
parsedStream: ParsedStream
): Promise<Stream | null> {
@@ -476,10 +501,8 @@ export class AIOStreams {
];
let stream: Stream;
const isMediaFlowEnabled =
this.config.mediaFlowConfig?.mediaFlowEnabled ||
Settings.DEFAULT_MEDIAFLOW_URL;
if (isMediaFlowEnabled && parsedStream?.url) {
const shouldProxy = this.shouldProxyStream(parsedStream);
if (shouldProxy) {
try {
const mediaFlowStream = this.createMediaFlowStream(
parsedStream,
@@ -137,23 +137,27 @@
}
.mediaFlowConfig {
border-width: 1px;
border-style: solid;
border-radius: var(--borderRadius);
border-color: #777777;
padding: 10px;
transition:
margin-top 0.4s,
opacity 0.3s,
transform 0.6s;
margin-top 0.6s,
opacity 0.6s,
transform 1s;
}
.mediaFlowConfig.hidden {
opacity: 0;
margin-top: -340px;
margin-top: -655px;
transform: scale(0);
transition:
margin-top 0.5s,
opacity 0.2s,
transform 0.4s;
}
.mediaFlowSection {
padding: 10px;
margin: 5px;
border-width: 1px;
border-style: solid;
border-radius: var(--borderRadius);
border-color: #777777;
}
+126 -93
View File
@@ -137,7 +137,6 @@ const defaultServices = serviceDetails.map((service) => ({
}));
export default function Configure() {
const [isClient, setIsClient] = useState(false);
const [resolutions, setResolutions] =
useState<Resolution[]>(defaultResolutions);
const [qualities, setQualities] = useState<Quality[]>(defaultQualities);
@@ -175,6 +174,12 @@ export default function Configure() {
const [mediaFlowProxyUrl, setmediaFlowProxyUrl] = useState<string>('');
const [mediaFlowApiPassword, setmediaFlowApiPassword] = useState<string>('');
const [mediaFlowPublicIp, setMediaFlowPublicIp] = useState<string>('');
const [mediaFlowProxiedAddons, setMediaFlowProxiedAddons] = useState<
string[] | null
>(null);
const [mediaFlowProxiedServices, setMediaFlowProxiedServices] = useState<
string[] | null
>(null);
const [disableButtons, setDisableButtons] = useState<boolean>(false);
const [manualManifestUrl, setManualManifestUrl] = useState<string | null>(
null
@@ -227,6 +232,8 @@ export default function Configure() {
proxyUrl: mediaFlowProxyUrl,
apiPassword: mediaFlowApiPassword,
publicIp: mediaFlowPublicIp,
proxiedAddons: mediaFlowProxiedAddons,
proxiedServices: mediaFlowProxiedServices,
},
addons,
services,
@@ -311,6 +318,7 @@ export default function Configure() {
const config = createConfig();
const { valid, errorCode, errorMessage } = validateConfig(config);
console.log('Config', config, 'was valid:', valid);
if (!valid) {
showToast(
errorMessage || 'Invalid config',
@@ -526,7 +534,6 @@ export default function Configure() {
// Load config from the window path if it exists
useEffect(() => {
setIsClient(true);
async function decodeConfig(config: string) {
let decodedConfig: Config;
if (config.startsWith('E-')) {
@@ -837,28 +844,12 @@ export default function Configure() {
</p>
</div>
<div>
{isClient ? ( // https://github.com/JedWatson/react-select/issues/5859
<MultiSelect
options={allowedLanguages
.sort((a, b) => a.localeCompare(b))
.map((language) => ({ value: language, label: language }))}
setValues={setPrioritisedLanguages}
/>
) : (
// render a fake select box until the actual one is rendered
<div
style={{
height: '42px',
margin: '0',
backgroundColor: 'white',
borderRadius: 'var(--borderRadius)',
display: 'flex',
alignItems: 'center',
}}
>
<p style={{ margin: '10px', color: '#808090' }}>Select...</p>
</div>
)}
<MultiSelect
options={allowedLanguages
.sort((a, b) => a.localeCompare(b))
.map((language) => ({ value: language, label: language }))}
setValues={setPrioritisedLanguages}
/>
</div>
</div>
<div style={{ marginBottom: '0px' }} className={styles.section}>
@@ -873,28 +864,12 @@ export default function Configure() {
</p>
</div>
<div>
{isClient ? (
<MultiSelect
options={allowedLanguages
.sort((a, b) => a.localeCompare(b))
.map((language) => ({ value: language, label: language }))}
setValues={setExcludedLanguages}
/>
) : (
// render a fake select box until the actual one is rendered
<div
style={{
height: '42px',
margin: '0',
backgroundColor: 'white',
borderRadius: 'var(--borderRadius)',
display: 'flex',
alignItems: 'center',
}}
>
<p style={{ margin: '10px', color: '#808090' }}>Select...</p>
</div>
)}
<MultiSelect
options={allowedLanguages
.sort((a, b) => a.localeCompare(b))
.map((language) => ({ value: language, label: language }))}
setValues={setExcludedLanguages}
/>
</div>
</div>
</div>
@@ -1100,61 +1075,119 @@ export default function Configure() {
<div
className={`${styles.mediaFlowConfig} ${mediaFlowEnabled ? '' : styles.hidden}`}
>
<div>
<div className={styles.mediaFlowSection}>
<div>
<h3 style={{ padding: '5px' }}>Proxy URL</h3>
<p style={{ padding: '5px' }}>
The URL of the MediaFlow proxy server
</p>
<div>
<h3 style={{ padding: '5px' }}>Proxy URL</h3>
<p style={{ padding: '5px' }}>
The URL of the MediaFlow proxy server
</p>
</div>
<div>
<CredentialInput
credential={mediaFlowProxyUrl}
setCredential={setmediaFlowProxyUrl}
inputProps={{
placeholder: 'Enter your MediaFlow proxy URL',
disabled: !mediaFlowEnabled,
}}
/>
</div>
</div>
<div>
<CredentialInput
credential={mediaFlowProxyUrl}
setCredential={setmediaFlowProxyUrl}
inputProps={{
placeholder: 'Enter your MediaFlow proxy URL',
disabled: !mediaFlowEnabled,
}}
/>
<div>
<h3 style={{ padding: '5px' }}>API Password</h3>
<p style={{ padding: '5px' }}>
Your MediaFlow&apos;s API password
</p>
</div>
<div>
<CredentialInput
credential={mediaFlowApiPassword}
setCredential={setmediaFlowApiPassword}
inputProps={{
placeholder: 'Enter your MediaFlow API password',
disabled: !mediaFlowEnabled,
}}
/>
</div>
</div>
<div>
<div>
<h3 style={{ padding: '5px' }}>Public IP (Optional)</h3>
<p style={{ padding: '5px' }}>
Configure this only when running MediaFlow locally with a
proxy service. Leave empty if MediaFlow is configured
locally without a proxy server or if it&apos;s hosted on a
remote server.
</p>
</div>
<div>
<CredentialInput
credential={mediaFlowPublicIp}
setCredential={setMediaFlowPublicIp}
inputProps={{
placeholder: 'Enter your MediaFlow public IP',
disabled: !mediaFlowEnabled,
}}
/>
</div>
</div>
</div>
<div>
<div className={styles.mediaFlowSection}>
<div>
<h3 style={{ padding: '5px' }}>API Password</h3>
<p style={{ padding: '5px' }}>
Your MediaFlow&apos;s API password
</p>
<div>
<h3 style={{ padding: '5px' }}>Proxy Addons (Optional)</h3>
<p style={{ padding: '5px' }}>
By default, all streams from every addon are proxied.
Choose specific addons here to proxy only their streams.
</p>
</div>
<div>
<MultiSelect
options={
addons.map((addon) => ({
value: `${addon.id}-${JSON.stringify(addon.options)}`,
label:
addon.options.addonName ||
addon.options.overrideName ||
addon.options.name ||
addon.id.charAt(0).toUpperCase() +
addon.id.slice(1),
})) || []
}
setValues={(selectedAddons) => {
setMediaFlowProxiedAddons(selectedAddons);
}}
/>
</div>
</div>
<div>
<CredentialInput
credential={mediaFlowApiPassword}
setCredential={setmediaFlowApiPassword}
inputProps={{
placeholder: 'Enter your MediaFlow API password',
disabled: !mediaFlowEnabled,
}}
/>
</div>
</div>
<div>
<div>
<h3 style={{ padding: '5px' }}>Public IP (Optional)</h3>
<p style={{ padding: '5px' }}>
Configure this only when running MediaFlow locally with a
proxy service. Leave empty if MediaFlow is configured
locally without a proxy server or if it&apos;s hosted on a
remote server.
</p>
</div>
<div>
<CredentialInput
credential={mediaFlowPublicIp}
setCredential={setMediaFlowPublicIp}
inputProps={{
placeholder: 'Enter your MediaFlow public IP',
disabled: !mediaFlowEnabled,
}}
/>
<div>
<h3 style={{ padding: '5px' }}>
Proxy Services (Optional)
</h3>
<p style={{ padding: '5px' }}>
By default, all streams whether they are from a serivce or
not are proxied. Choose which services you want to proxy
through MediaFlow. Selecting None will also proxy streams
that are not (detected to be) from a service.
</p>
</div>
<div>
<MultiSelect
options={[
{ value: 'none', label: 'None' },
...serviceDetails.map((service) => ({
value: service.id,
label: service.name,
})),
]}
setValues={(selectedServices) => {
setMediaFlowProxiedServices(selectedServices);
}}
/>
</div>
</div>
</div>
</div>
@@ -175,7 +175,7 @@ const AddonsList: React.FC<AddonsListProps> = ({
updateOption(
index,
option.id,
e.target.checked ? 'true' : 'false'
e.target.checked ? 'true' : undefined
)
}
className={styles.checkbox}
@@ -188,7 +188,11 @@ const AddonsList: React.FC<AddonsListProps> = ({
<CredentialInput
credential={addon.options[option.id] || ''}
setCredential={(value) =>
updateOption(index, option.id, value)
updateOption(
index,
option.id,
value ? value : undefined
)
}
/>
) : (
@@ -1,4 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from 'react';
import Select, { StylesConfig } from 'react-select';
const selectStyles: StylesConfig = {
@@ -79,19 +80,41 @@ interface MultiSelectProps {
}
const MultiSelect: React.FC<MultiSelectProps> = ({ options, setValues }) => {
const [isClient, setIsClient] = useState(false);
useEffect(() => {
setIsClient(true);
}, []);
return (
<Select
isMulti
closeMenuOnSelect={false}
options={options}
onChange={(selectedOptions: any) => {
const selectedLanguages = selectedOptions.map(
(option: any) => option.value
);
setValues(selectedLanguages);
}}
styles={selectStyles}
/>
<>
{isClient ? (
// https://github.com/JedWatson/react-select/issues/5859
<Select
isMulti
closeMenuOnSelect={false}
options={options}
onChange={(selectedOptions: any) => {
const selectedLanguages = selectedOptions.map(
(option: any) => option.value
);
setValues(selectedLanguages);
}}
styles={selectStyles}
/>
) : (
<div
style={{
height: '42px',
margin: '0',
backgroundColor: 'white',
borderRadius: 'var(--borderRadius)',
display: 'flex',
alignItems: 'center',
}}
>
<p style={{ margin: '10px', color: '#808090' }}>Select...</p>
</div>
)}
</>
);
};
+2
View File
@@ -154,6 +154,8 @@ export interface Config {
proxyUrl: string;
apiPassword: string;
publicIp: string;
proxiedAddons: string[] | null;
proxiedServices: string[] | null;
};
addons: {
id: string;
+2
View File
@@ -138,5 +138,7 @@ export function getMediaFlowConfig(userConfig: Config) {
apiPassword:
mediaFlowConfig?.apiPassword || Settings.DEFAULT_MEDIAFLOW_API_PASSWORD,
publicIp: mediaFlowConfig?.publicIp || Settings.DEFAULT_MEDIAFLOW_PUBLIC_IP,
proxiedAddons: mediaFlowConfig?.proxiedAddons || null,
proxiedServices: mediaFlowConfig?.proxiedServices || null,
};
}