From da968232adb0c447692e4a2be1e21e42267a2b4a Mon Sep 17 00:00:00 2001
From: Izan Gil <66965250+SrIzan10@users.noreply.github.com>
Date: Wed, 22 Apr 2026 21:27:42 +0200
Subject: [PATCH] feat(bs): server selector
---
.../src/app/(ui)/(protected)/stream/page.tsx | 37 ++++++++++++++++++-
.../src/lib/hooks/useScreensharePublisher.ts | 16 +++++---
apps/web/src/lib/utils/mediamtx/client.ts | 17 +++++++++
3 files changed, 63 insertions(+), 7 deletions(-)
diff --git a/apps/web/src/app/(ui)/(protected)/stream/page.tsx b/apps/web/src/app/(ui)/(protected)/stream/page.tsx
index d2a8be6..98d18e0 100644
--- a/apps/web/src/app/(ui)/(protected)/stream/page.tsx
+++ b/apps/web/src/app/(ui)/(protected)/stream/page.tsx
@@ -3,12 +3,25 @@
import { useEffect, useState } from 'react';
import { ChannelSelect } from '@/components/app/ChannelSelect/ChannelSelect';
import { Button } from '@/components/ui/button';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
import { useChannelStreamKey } from '@/lib/hooks/useChannelStreamKey';
import { useOwnedChannels } from '@/lib/hooks/useUserList';
import { useScreensharePublisher } from '@/lib/hooks/useScreensharePublisher';
+import { getMediamtxClientRegionOptions } from '@/lib/utils/mediamtx/client';
+import type { MediaMTXRegion } from '@/lib/utils/mediamtx/regions';
export default function Page() {
+ const serverOptions = getMediamtxClientRegionOptions();
const [selectedChannel, setSelectedChannel] = useState('');
+ const [selectedRegion, setSelectedRegion] = useState(
+ serverOptions[0]?.value ?? 'hq'
+ );
const { channels, isLoading: isLoadingChannels } = useOwnedChannels();
const ownedChannels = channels.map(({ channel }) => channel);
const {
@@ -28,10 +41,12 @@ export default function Page() {
stopPublishing,
} = useScreensharePublisher({
channelName: selectedChannel,
+ region: selectedRegion,
streamKey,
});
const hasChannels = ownedChannels.length > 0;
+ const hasServerOptions = serverOptions.length > 0;
const canStartPublishing =
!isSessionActive && Boolean(selectedChannel) && Boolean(streamKey) && !isLoadingStreamKey;
const channelPlaceholder = isLoadingChannels ? 'Loading channels...' : 'Select a channel';
@@ -53,7 +68,7 @@ export default function Page() {
broadcast.
-
+
+
+
+
Server
+
+
{!hasChannels && !isLoadingChannels ? (
diff --git a/apps/web/src/lib/hooks/useScreensharePublisher.ts b/apps/web/src/lib/hooks/useScreensharePublisher.ts
index 5000430..f370196 100644
--- a/apps/web/src/lib/hooks/useScreensharePublisher.ts
+++ b/apps/web/src/lib/hooks/useScreensharePublisher.ts
@@ -1,8 +1,8 @@
-// completely generated by gpt-5.4
-
'use client';
import { useCallback, useEffect, useRef, useState } from 'react';
+import { getMediamtxClientEnvs } from '@/lib/utils/mediamtx/client';
+import type { MediaMTXRegion } from '@/lib/utils/mediamtx/regions';
import MediaMTXWebRTCPublisher from '@/lib/utils/mediamtx/webrtc';
const HLS_COMPATIBLE_VIDEO_CODECS = [
@@ -23,6 +23,7 @@ const DISPLAY_MEDIA_OPTIONS: ScreenCaptureOptions = {
export function useScreensharePublisher({
channelName,
+ region,
streamKey,
}: UseScreensharePublisherOptions) {
const previewRef = useRef
(null);
@@ -123,7 +124,7 @@ export function useScreensharePublisher({
commitCaptureStream(stream);
const publisher = new MediaMTXWebRTCPublisher({
- url: getWhipUrl(channelName),
+ url: getWhipUrl(channelName, region),
stream,
videoCodec,
videoBitrate: 2000,
@@ -155,7 +156,7 @@ export function useScreensharePublisher({
setPublishState('idle');
setError(getErrorMessage(err, 'Failed to start publishing'));
}
- }, [channelName, commitCaptureStream, disposeCurrentSession, streamKey]);
+ }, [channelName, commitCaptureStream, disposeCurrentSession, region, streamKey]);
const changeSource = useCallback(async () => {
const publisher = publisherRef.current;
@@ -204,8 +205,10 @@ async function requestCaptureStream() {
return navigator.mediaDevices.getDisplayMedia(DISPLAY_MEDIA_OPTIONS as DisplayMediaStreamOptions);
}
-function getWhipUrl(channelName: string) {
- return `http://localhost:8889/${encodeURIComponent(channelName)}/whip`;
+function getWhipUrl(channelName: string, region: MediaMTXRegion) {
+ const { whip } = getMediamtxClientEnvs(region);
+
+ return `${whip.replace(/\/$/, '')}/${encodeURIComponent(channelName)}/whip`;
}
function stopTracks(stream: MediaStream | null) {
@@ -243,6 +246,7 @@ type PublishState = 'idle' | 'connecting' | 'live' | 'switching';
type UseScreensharePublisherOptions = {
channelName: string;
+ region: MediaMTXRegion;
streamKey?: string | null;
};
diff --git a/apps/web/src/lib/utils/mediamtx/client.ts b/apps/web/src/lib/utils/mediamtx/client.ts
index 73edd2c..5e623f0 100644
--- a/apps/web/src/lib/utils/mediamtx/client.ts
+++ b/apps/web/src/lib/utils/mediamtx/client.ts
@@ -4,15 +4,23 @@ import { getEnv } from '@/lib/env';
export interface MediaMTXClientEnvs {
publicUrl: string;
ingestRoute: string;
+ whip: string;
emoji: string;
string: string;
}
+export interface MediaMTXClientRegionOption {
+ value: MediaMTXRegion;
+ emoji: string;
+ label: string;
+}
+
export function getMediamtxClientEnvs(region: MediaMTXRegion = 'hq'): MediaMTXClientEnvs {
const envs: Record = {
hq: {
publicUrl: getEnv('NEXT_PUBLIC_MEDIAMTX_URL_HQ')!,
ingestRoute: getEnv('NEXT_PUBLIC_MEDIAMTX_INGEST_ROUTE_HQ')!,
+ whip: getEnv('NEXT_PUBLIC_MEDIAMTX_WHIP_ROUTE_HQ')!,
emoji: '🇺🇸',
string: 'HQ Server A',
},
@@ -27,3 +35,12 @@ export function getMediamtxClientEnvs(region: MediaMTXRegion = 'hq'): MediaMTXCl
return regionEnvs;
}
+export function getMediamtxClientRegionOptions(): MediaMTXClientRegionOption[] {
+ return [
+ {
+ value: 'hq',
+ emoji: '🇺🇸',
+ label: 'HQ Server A',
+ },
+ ];
+}