/* eslint-disable @typescript-eslint/no-explicit-any */ 'use client'; import { useState, useEffect } from 'react'; import Image from 'next/image'; import styles from './page.module.css'; import { Config, Resolution, SortBy, Quality, VisualTag, AudioTag, Encode, ServiceDetail, ServiceCredential, StreamType, } from '@aiostreams/types'; import SortableCardList from '../../components/SortableCardList'; import ServiceInput from '../../components/ServiceInput'; import AddonsList from '../../components/AddonsList'; import { Slide, ToastContainer, toast } from 'react-toastify'; import showToast, { toastOptions } from '@/components/Toasts'; import addonPackage from '../../../../../package.json'; import { formatSize } from '@aiostreams/formatters'; import { allowedFormatters, allowedLanguages, validateConfig, } from '@aiostreams/config'; import { addonDetails, isValueEncrypted, serviceDetails, Settings, } from '@aiostreams/utils'; import Slider from '@/components/Slider'; import CredentialInput from '@/components/CredentialInput'; import CreateableSelect from '@/components/CreateableSelect'; import MultiSelect from '@/components/MutliSelect'; import InstallWindow from '@/components/InstallWindow'; import FormatterPreview from '@/components/FormatterPreview'; import CustomFormatter from '@/components/CustomFormatter'; const version = addonPackage.version; interface Option { label: string; value: string; } const defaultQualities: Quality[] = [ { 'BluRay REMUX': true }, { BluRay: true }, { 'WEB-DL': true }, { WEBRip: true }, { HDRip: true }, { 'HC HD-Rip': true }, { DVDRip: true }, { HDTV: true }, { CAM: true }, { TS: true }, { TC: true }, { SCR: true }, { Unknown: true }, ]; const defaultVisualTags: VisualTag[] = [ { 'HDR+DV': true }, { 'HDR10+': true }, { DV: true }, { HDR10: true }, { HDR: true }, { '10bit': true }, { '3D': true }, { IMAX: true }, { AI: true }, { SDR: true }, ]; const defaultAudioTags: AudioTag[] = [ { Atmos: true }, { 'DD+': true }, { DD: true }, { 'DTS-HD MA': true }, { 'DTS-HD': true }, { DTS: true }, { TrueHD: true }, { '5.1': true }, { '7.1': true }, { FLAC: true }, { AAC: true }, ]; const defaultEncodes: Encode[] = [ { AV1: true }, { HEVC: true }, { AVC: true }, { Xvid: true }, { DivX: true }, { 'H-OU': true }, { 'H-SBS': true }, { Unknown: true }, ]; const defaultSortCriteria: SortBy[] = [ { cached: true, direction: 'desc' }, { personal: true, direction: 'desc' }, { resolution: true }, { language: true }, { size: true, direction: 'desc' }, { streamType: false }, { visualTag: false }, { service: false }, { audioTag: false }, { encode: false }, { quality: false }, { seeders: false, direction: 'desc' }, { addon: false }, { regexSort: false, direction: 'desc' }, ]; const defaultResolutions: Resolution[] = [ { '2160p': true }, { '1440p': true }, { '1080p': true }, { '720p': true }, { '480p': true }, { Unknown: true }, ]; const defaultServices = serviceDetails.map((service) => ({ name: service.name, id: service.id, enabled: false, credentials: {}, })); const defaultStreamTypes: StreamType[] = [ { usenet: true }, { debrid: true }, { unknown: true }, { p2p: true }, { live: true }, ]; export default function Configure() { const [formatterOptions, setFormatterOptions] = useState( allowedFormatters.filter((f) => f !== 'imposter') ); const [streamTypes, setStreamTypes] = useState(defaultStreamTypes); const [resolutions, setResolutions] = useState(defaultResolutions); const [qualities, setQualities] = useState(defaultQualities); const [visualTags, setVisualTags] = useState(defaultVisualTags); const [audioTags, setAudioTags] = useState(defaultAudioTags); const [encodes, setEncodes] = useState(defaultEncodes); const [sortCriteria, setSortCriteria] = useState(defaultSortCriteria); const [formatter, setFormatter] = useState(); const [services, setServices] = useState(defaultServices); const [onlyShowCachedStreams, setOnlyShowCachedStreams] = useState(false); const [prioritisedLanguages, setPrioritisedLanguages] = useState< string[] | null >(null); const [excludedLanguages, setExcludedLanguages] = useState( null ); const [addons, setAddons] = useState([]); /* const [maxSize, setMaxSize] = useState(null); const [minSize, setMinSize] = useState(null); */ const [maxMovieSize, setMaxMovieSize] = useState(null); const [minMovieSize, setMinMovieSize] = useState(null); const [maxEpisodeSize, setMaxEpisodeSize] = useState(null); const [minEpisodeSize, setMinEpisodeSize] = useState(null); const [cleanResults, setCleanResults] = useState(false); const [maxResultsPerResolution, setMaxResultsPerResolution] = useState< number | null >(null); const [excludeFilters, setExcludeFilters] = useState([]); const [strictIncludeFilters, setStrictIncludeFilters] = useState< readonly Option[] >([]); /* const [prioritiseIncludeFilters, setPrioritiseIncludeFilters] = useState< readonly Option[] >([]); */ const [mediaFlowEnabled, setMediaFlowEnabled] = useState(false); const [mediaFlowProxyUrl, setMediaFlowProxyUrl] = useState(''); const [mediaFlowApiPassword, setMediaFlowApiPassword] = useState(''); const [mediaFlowPublicIp, setMediaFlowPublicIp] = useState(''); const [mediaFlowProxiedAddons, setMediaFlowProxiedAddons] = useState< string[] | null >(null); const [mediaFlowProxiedServices, setMediaFlowProxiedServices] = useState< string[] | null >(null); const [stremThruEnabled, setStremThruEnabled] = useState(false); const [stremThruUrl, setStremThruUrl] = useState(''); const [stremThruCredential, setStremThruCredential] = useState(''); const [stremThruPublicIp, setStremThruPublicIp] = useState(''); const [stremThruProxiedAddons, setStremThruProxiedAddons] = useState< string[] | null >(null); const [stremThruProxiedServices, setStremThruProxiedServices] = useState< string[] | null >(null); const [overrideName, setOverrideName] = useState(''); const [apiKey, setApiKey] = useState(''); const [disableButtons, setDisableButtons] = useState(false); const [maxMovieSizeSlider, setMaxMovieSizeSlider] = useState( Settings.MAX_MOVIE_SIZE ); const [maxEpisodeSizeSlider, setMaxEpisodeSizeSlider] = useState( Settings.MAX_EPISODE_SIZE ); const [choosableAddons, setChoosableAddons] = useState( addonDetails.map((addon) => addon.id) ); const [showApiKeyInput, setShowApiKeyInput] = useState(false); const [manifestUrl, setManifestUrl] = useState(null); const [regexFilters, setRegexFilters] = useState<{ excludePattern?: string; includePattern?: string; }>({}); const [regexSortPatterns, setRegexSortPatterns] = useState(''); useEffect(() => { // get config from the server fetch('/get-addon-config') .then((res) => res.json()) .then((data) => { if (data.success) { setMaxMovieSizeSlider(data.maxMovieSize); setMaxEpisodeSizeSlider(data.maxEpisodeSize); setShowApiKeyInput(data.apiKeyRequired); // filter out 'torrentio' from choosableAddons if torrentioDisabled is true if (data.torrentioDisabled) { setChoosableAddons( addonDetails .map((addon) => addon.id) .filter((id) => id !== 'torrentio') ); } } }); }, []); const createConfig = (): Config => { const config = { apiKey: apiKey, overrideName, streamTypes, resolutions, qualities, visualTags, audioTags, encodes, sortBy: sortCriteria, onlyShowCachedStreams, prioritisedLanguages, excludedLanguages, maxMovieSize, minMovieSize, maxEpisodeSize, minEpisodeSize, cleanResults, maxResultsPerResolution, strictIncludeFilters: strictIncludeFilters.length > 0 ? strictIncludeFilters.map((filter) => filter.value) : null, excludeFilters: excludeFilters.length > 0 ? excludeFilters.map((filter) => filter.value) : null, formatter: formatter || 'gdrive', mediaFlowConfig: { mediaFlowEnabled: mediaFlowEnabled && !stremThruEnabled, proxyUrl: mediaFlowProxyUrl, apiPassword: mediaFlowApiPassword, publicIp: mediaFlowPublicIp, proxiedAddons: mediaFlowProxiedAddons, proxiedServices: mediaFlowProxiedServices, }, stremThruConfig: { stremThruEnabled: stremThruEnabled && !mediaFlowEnabled, url: stremThruUrl, credential: stremThruCredential, publicIp: stremThruPublicIp, proxiedAddons: stremThruProxiedAddons, proxiedServices: stremThruProxiedServices, }, addons, services, regexFilters: regexFilters.excludePattern || regexFilters.includePattern ? { excludePattern: regexFilters.excludePattern || undefined, includePattern: regexFilters.includePattern || undefined, } : undefined, regexSortPatterns: regexSortPatterns, }; return config; }; const fetchWithTimeout = async ( url: string, options: RequestInit | undefined, timeoutMs = 30000 ) => { const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), timeoutMs); try { console.log('Fetching', url, `with data: ${options?.body}`); const res = await fetch(url, { ...options, signal: controller.signal }); clearTimeout(timeout); return res; } catch { console.log('Clearing timeout'); return clearTimeout(timeout); } }; const getManifestUrl = async ( protocol = window.location.protocol, root = window.location.host ): Promise<{ success: boolean; manifest: string | null; message: string | null; }> => { const config = createConfig(); const { valid, errorMessage } = validateConfig(config, 'client'); if (!valid) { return { success: false, manifest: null, message: errorMessage || 'Invalid config', }; } console.log('Config', config); setDisableButtons(true); try { const encryptPath = `/encrypt-user-data`; const response = await fetchWithTimeout(encryptPath, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ data: JSON.stringify(config) }), }); if (!response) { throw new Error('encrypt-user-data failed: no response within timeout'); } if (!response.ok) { throw new Error( `encrypt-user-data failed with status ${response.status} and statusText ${response.statusText}` ); } const data = await response.json(); if (!data.success) { if (data.error) { return { success: false, manifest: null, message: data.error || 'Failed to generate config', }; } throw new Error(`Encryption service failed, ${data.message}`); } const configString = data.data; return { success: true, manifest: `${protocol}//${root}/${configString}/manifest.json`, message: null, }; } catch (error: any) { console.error(error); return { success: false, manifest: null, message: error.message || 'Failed to encrypt config', }; } }; const loadValidValuesFromObject = ( object: { [key: string]: boolean }[] | undefined, validValues: { [key: string]: boolean }[] ) => { if (!object) { return validValues; } const mergedValues = object.filter((value) => validValues.some((validValue) => Object.keys(validValue).includes(Object.keys(value)[0]) ) ); for (const validValue of validValues) { if ( !mergedValues.some( (value) => Object.keys(value)[0] === Object.keys(validValue)[0] ) ) { mergedValues.push(validValue); } } return mergedValues; }; const loadValidSortCriteria = (sortCriteria: Config['sortBy']) => { if (!sortCriteria) { return defaultSortCriteria; } const mergedValues = sortCriteria .map((sort) => { const defaultSort = defaultSortCriteria.find( (defaultSort) => Object.keys(defaultSort)[0] === Object.keys(sort)[0] ); if (!defaultSort) { return null; } return { ...sort, direction: defaultSort?.direction // only load direction if it exists in the defaultSort ? sort.direction || defaultSort.direction : undefined, }; }) .filter((sort) => sort !== null); defaultSortCriteria.forEach((defaultSort) => { if ( !mergedValues.some( (sort) => Object.keys(sort)[0] === Object.keys(defaultSort)[0] ) ) { mergedValues.push({ ...defaultSort, direction: defaultSort.direction || undefined, }); } }); return mergedValues; }; const validateValue = (value: string | null, validValues: string[]) => { if (!value) { return null; } return validValues.includes(value) ? value : null; }; const loadValidServices = (services: Config['services']) => { if (!services) { return defaultServices; } const mergedServices = services // filter out services that are not in serviceDetails .filter((service) => defaultServices.some((ds) => ds.id === service.id)) .map((service) => { const defaultService = defaultServices.find( (ds) => ds.id === service.id ); if (!defaultService) { return null; } // only load enabled and credentials from the previous config return { ...defaultService, enabled: service.enabled, credentials: service.credentials, }; }) .filter((service) => service !== null); // add any services that are in defaultServices but not in services defaultServices.forEach((defaultService) => { if (!mergedServices.some((service) => service.id === defaultService.id)) { mergedServices.push(defaultService); } }); return mergedServices; }; const loadValidAddons = (addons: Config['addons']) => { if (!addons) { return []; } return addons.filter((addon) => addonDetails.some((detail) => detail.id === addon.id) ); }; // Load config from the window path if it exists useEffect(() => { async function decodeConfig(config: string) { let decodedConfig: Config; if (isValueEncrypted(config) || config.startsWith('B-')) { throw new Error('Encrypted Config Not Supported'); } else { decodedConfig = JSON.parse( Buffer.from(decodeURIComponent(config), 'base64').toString('utf-8') ); } return decodedConfig; } function loadFromConfig(decodedConfig: Config) { console.log('Loaded config', decodedConfig); setOverrideName(decodedConfig.overrideName || ''); setStreamTypes( loadValidValuesFromObject(decodedConfig.streamTypes, defaultStreamTypes) ); setResolutions( loadValidValuesFromObject(decodedConfig.resolutions, defaultResolutions) ); setQualities( loadValidValuesFromObject(decodedConfig.qualities, defaultQualities) ); setVisualTags( loadValidValuesFromObject(decodedConfig.visualTags, defaultVisualTags) ); setAudioTags( loadValidValuesFromObject(decodedConfig.audioTags, defaultAudioTags) ); setEncodes( loadValidValuesFromObject(decodedConfig.encodes, defaultEncodes) ); setSortCriteria(loadValidSortCriteria(decodedConfig.sortBy)); setOnlyShowCachedStreams(decodedConfig.onlyShowCachedStreams || false); // create an array for prioritised languages. if the old prioritiseLanguage is set, add it to the array const finalPrioritisedLanguages = decodedConfig.prioritisedLanguages || []; if (decodedConfig.prioritiseLanguage) { finalPrioritisedLanguages.push(decodedConfig.prioritiseLanguage); } setPrioritisedLanguages( finalPrioritisedLanguages.filter((lang) => allowedLanguages.includes(lang) ) || null ); setExcludedLanguages( decodedConfig.excludedLanguages?.filter((lang) => allowedLanguages.includes(lang) ) || null ); setStrictIncludeFilters( decodedConfig.strictIncludeFilters?.map((filter) => ({ label: filter, value: filter, })) || [] ); setExcludeFilters( decodedConfig.excludeFilters?.map((filter) => ({ label: filter, value: filter, })) || [] ); setRegexFilters(decodedConfig.regexFilters || {}); setRegexSortPatterns(decodedConfig.regexSortPatterns || ''); setServices(loadValidServices(decodedConfig.services)); setMaxMovieSize( decodedConfig.maxMovieSize || decodedConfig.maxSize || null ); setMinMovieSize( decodedConfig.minMovieSize || decodedConfig.minSize || null ); setMaxEpisodeSize( decodedConfig.maxEpisodeSize || decodedConfig.maxSize || null ); setMinEpisodeSize( decodedConfig.minEpisodeSize || decodedConfig.minSize || null ); setAddons(loadValidAddons(decodedConfig.addons)); setCleanResults(decodedConfig.cleanResults || false); setMaxResultsPerResolution(decodedConfig.maxResultsPerResolution || null); setMediaFlowEnabled( decodedConfig.mediaFlowConfig?.mediaFlowEnabled || false ); setMediaFlowProxyUrl(decodedConfig.mediaFlowConfig?.proxyUrl || ''); setMediaFlowApiPassword(decodedConfig.mediaFlowConfig?.apiPassword || ''); setMediaFlowPublicIp(decodedConfig.mediaFlowConfig?.publicIp || ''); setMediaFlowProxiedAddons( decodedConfig.mediaFlowConfig?.proxiedAddons || null ); setMediaFlowProxiedServices( decodedConfig.mediaFlowConfig?.proxiedServices || null ); setStremThruEnabled( decodedConfig.stremThruConfig?.stremThruEnabled || false ); setStremThruUrl(decodedConfig.stremThruConfig?.url || ''); setStremThruCredential(decodedConfig.stremThruConfig?.credential || ''); setStremThruPublicIp(decodedConfig.stremThruConfig?.publicIp || ''); setApiKey(decodedConfig.apiKey || ''); // set formatter const formatterValue = validateValue( decodedConfig.formatter, allowedFormatters ); if ( decodedConfig.formatter.startsWith('custom') && decodedConfig.formatter.length > 7 ) { setFormatter(decodedConfig.formatter); } else if (formatterValue) { setFormatter(formatterValue); } } const path = window.location.pathname; try { const configMatch = path.match(/\/([^/]+)\/configure/); if (configMatch) { const config = configMatch[1]; decodeConfig(config).then(loadFromConfig); } } catch (error) { console.error('Failed to load config', error); } }, []); return (
AIOStreams Logo
setOverrideName(e.target.value)} style={{ border: 'none', backgroundColor: 'black', color: 'white', fontWeight: 'bold', background: 'black', height: '30px', textAlign: 'center', fontSize: '30px', padding: '0', maxWidth: '300px', width: 'auto', margin: '0 auto', }} size={overrideName?.length < 8 ? 8 : overrideName?.length || 8} > { window.open( `https://github.com/Viren070/AIOStreams/releases/tag/v${version}`, '_blank', 'noopener noreferrer' ); }} > v{version}
{process.env.NEXT_PUBLIC_BRANDING && (
)}

AIOStreams, the all-in-one streaming addon for Stremio. Combine your streams from all your addons into one and filter them by resolution, quality, visual tags and more.

This addon will return any result from the addons you enable. These can be P2P results, direct links, or anything else. Results that are P2P are marked as P2P, however.

This addon also has no persistence. Nothing you enter here is stored. They are encrypted within the manifest URL and are only used to retrieve streams from any addons you enable.

Configuration Guide {' | '} GitHub {' | '} Stremio Guide

Services

Enable the services you have accounts with and enter your credentials.

{services.map((service, index) => ( { const newServices = [...services]; const serviceIndex = newServices.findIndex( (s) => s.id === service.id ); newServices[serviceIndex] = { ...service, enabled }; setServices(newServices); }} fields={ serviceDetails .find((detail: ServiceDetail) => detail.id === service.id) ?.credentials.map((credential: ServiceCredential) => ({ label: credential.label, link: credential.link, value: service.credentials[credential.id] || '', setValue: (value) => { const newServices = [...services]; const serviceIndex = newServices.findIndex( (s) => s.id === service.id ); newServices[serviceIndex] = { ...service, credentials: { ...service.credentials, [credential.id]: value, }, }; setServices(newServices); }, })) || [] } moveService={(direction) => { const newServices = [...services]; const serviceIndex = newServices.findIndex( (s) => s.id === service.id ); const [movedService] = newServices.splice(serviceIndex, 1); if (direction === 'up' && serviceIndex > 0) { newServices.splice(serviceIndex - 1, 0, movedService); } else if ( direction === 'down' && serviceIndex < newServices.length ) { newServices.splice(serviceIndex + 1, 0, movedService); } setServices(newServices); }} canMoveUp={index > 0} canMoveDown={index < services.length - 1} signUpLink={ serviceDetails.find((detail) => detail.id === service.id) ?.signUpLink } /> ))}

Only Show Cached Streams

Only show streams that are cached by the enabled services.

setOnlyShowCachedStreams(e.target.checked)} // move to the right style={{ marginLeft: 'auto', marginRight: '20px', width: '25px', height: '25px', }} />

Addons

Stream Types

Choose which stream types you want to see and reorder their priority if needed. You can uncheck P2P to remove P2P streams from the results.

Resolutions

Choose which resolutions you want to see and reorder their priority if needed.

Qualities

Choose which qualities you want to see and reorder their priority if needed.

Visual Tags

Choose which visual tags you want to see and reorder their priority if needed.

Audio Tags

Choose which audio tags you want to see and reorder their priority if needed.

Encodes

Choose which encodes you want to see and reorder their priority if needed.

Sort By

Choose the criteria by which to sort streams.

Languages

Choose which languages you want to prioritise and exclude from the results

Prioritise Languages

Any results that are detected to have one of the prioritised languages will be sorted according to your sort criteria. You must have the Langage sort criteria enabled for this to work. If there are multiple results with a different prioritised language, the order is determined by the order of the prioritised languages.

a.localeCompare(b)) .map((language) => ({ value: language, label: language }))} setValues={setPrioritisedLanguages} values={prioritisedLanguages || []} />

Exclude Languages

Any results that are detected to have an excluded language will be removed from the results. A result will only be excluded if it only has one of or more of the excluded languages. If it contains a language that is not excluded, it will still be included.

a.localeCompare(b)) .map((language) => ({ value: language, label: language }))} setValues={setExcludedLanguages} values={excludedLanguages || []} />

Keyword Filter

Filter streams by keywords. You can exclude streams that contain specific keywords or only include streams that contain specific keywords.

Exclude Filter

Enter keywords to filter streams by. Streams that contain any of the keywords will be excluded.

Include Filter

Enter keywords to filter streams by. Streams that do not contain any of the keywords will be excluded.

{showApiKeyInput && (

Regex Filtering

Configure regex patterns to filter streams. These filters will be applied in addition to keyword filters.

Exclude Pattern

Enter a regex pattern to exclude streams. Streams will be excluded if their filename OR indexers match this pattern.

setRegexFilters({ ...regexFilters, excludePattern: e.target.value, }) } placeholder="Example: \b(0neshot|1XBET)\b" className={styles.input} />

Example patterns:
- \b(0neshot|1XBET|24xHD)\b (exclude 0neshot, 1XBET, and 24xHD releases)
- ^.*Hi10.*$ (exclude Hi10 profile releases)

Include Pattern

Enter a regex pattern to include streams. Only streams whose filename or indexers match this pattern will be included.

setRegexFilters({ ...regexFilters, includePattern: e.target.value, }) } placeholder="Example: \b(3L|BiZKiT)\b" className={styles.input} />

Example patterns:
- \b(3L|BiZKiT|BLURANiUM)\b (only include 3L, BiZKiT, and BLURANiUM releases)

)} {showApiKeyInput && (

Regex Sort Patterns

Enter a space separated list of regex patterns, optionally with a name, to sort streams by. Streams will be sorted based on the order of matching patterns. Matching files will come first in descending order, and last in ascending order for each pattern. You can give each regex a name using the following syntax:

regexName{`<::>`}regexPattern

For example, 3L{`<::>`}\b(3L|BiZKiT)\b will sort streams matching the regex \b(3L|BiZKiT)\b first and those streams will have the {`regexMatched`} property with the value 3L in the custom formatter.

setRegexSortPatterns(e.target.value)} placeholder="Example: \b(3L|BiZKiT)\b \b(FraMeSToR)\b" style={{ width: '97.5%', padding: '5px', marginLeft: '5px', }} className={styles.input} />

Example patterns:
- \b(3L|BiZKiT|BLURANiUM)\b \b(FraMeSToR)\b (sort 3L/BiZKiT/BLURANiUM releases first, then FraMeSToR releases)

)}

Size Filter

Filter streams by size. Leave the maximum and minimum size sliders at opposite ends to disable the filter.

Minimum movie size: {formatSize(minMovieSize || 0)}
Maximum movie size:{' '} {maxMovieSize === null ? 'Unlimited' : formatSize(maxMovieSize)}
Minimum episode size: {formatSize(minEpisodeSize || 0)}
Maximum episode size:{' '} {maxEpisodeSize === null ? 'Unlimited' : formatSize(maxEpisodeSize)}

Limit results per resolution

Limit the number of results per resolution. Leave empty to show all results.

setMaxResultsPerResolution( e.target.value ? parseInt(e.target.value) : null ) } style={{ width: '100px', height: '30px', }} />

Formatter

Change how your stream results are f { if (formatterOptions.includes('imposter')) { return; } showToast( "What's this doing here....?", 'info', 'ImposterFormatter' ); setFormatterOptions([...formatterOptions, 'imposter']); }} > ◌ rmatted.

{formatter?.startsWith('custom') && ( )}

Clean Results

Attempt to remove duplicate results. For a given file with duplicate streams: one uncached stream from all uncached streams is selected per provider. One cached stream from only one provider is selected. For duplicates without a provider, one stream is selected at random.

setCleanResults(e.target.checked)} // move to the right style={{ marginLeft: 'auto', marginRight: '20px', width: '25px', height: '25px', }} />

MediaFlow

Use MediaFlow to proxy your streams

{ setMediaFlowEnabled(e.target.checked); }} style={{ width: '25px', height: '25px', }} />
{

Proxy URL

The URL of the MediaFlow proxy server

API Password

Your MediaFlow's API password

Public IP (Optional)

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's hosted on a remote server.

Proxy Addons (Optional)

By default, all streams from every addon are proxied. Choose specific addons here to proxy only their streams.

({ 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.length === 0 ? null : selectedAddons ); }} values={mediaFlowProxiedAddons || undefined} />

Proxy Services (Optional)

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.

({ value: service.id, label: service.name, })), ]} setValues={(selectedServices) => { setMediaFlowProxiedServices( selectedServices.length === 0 ? null : selectedServices ); }} values={mediaFlowProxiedServices || undefined} />
}

StremThru

Use StremThru to proxy your streams

{ setStremThruEnabled(e.target.checked); }} style={{ width: '25px', height: '25px', }} />
{

StremThru URL

The URL of the StremThru server

Credential

Your StremThru Credential

Public IP (Optional)

Set the publicly exposed IP for StremThru server.

Proxy Addons (Optional)

By default, all streams from every addon are proxied. Choose specific addons here to proxy only their streams.

({ 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) => { setStremThruProxiedAddons( selectedAddons.length === 0 ? null : selectedAddons ); }} values={stremThruProxiedAddons || undefined} />

Proxy Services (Optional)

By default, all streams whether they are from a serivce or not are proxied. Choose which services you want to proxy through StremThru. Selecting None will also proxy streams that are not (detected to be) from a service.

({ value: service.id, label: service.name, })), ]} setValues={(selectedServices) => { setStremThruProxiedServices( selectedServices.length === 0 ? null : selectedServices ); }} values={stremThruProxiedServices || undefined} />
}
{showApiKeyInput && (

API Key

Enter your AIOStreams API Key to install and use this addon. You need to enter the one that is set in the{' '} API_KEY environment variable.

)}
); }