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.

-
+

Channel

+ +
+

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', + }, + ]; +}