feat(bs): server selector

This commit is contained in:
2026-04-22 21:27:42 +02:00
parent be758685d1
commit da968232ad
3 changed files with 63 additions and 7 deletions

View File

@@ -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<MediaMTXRegion>(
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.
</p>
<div className="grid gap-4 md:grid-cols-[220px_1fr]">
<div className="grid gap-4 md:grid-cols-[220px_220px]">
<div className="space-y-2">
<p className="text-sm font-medium">Channel</p>
<ChannelSelect
@@ -65,6 +80,26 @@ export default function Page() {
triggerClassName="w-full"
/>
</div>
<div className="space-y-2">
<p className="text-sm font-medium">Server</p>
<Select
value={selectedRegion}
onValueChange={(value) => setSelectedRegion(value as MediaMTXRegion)}
disabled={isSessionActive || !hasServerOptions}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="Select server" />
</SelectTrigger>
<SelectContent>
{serverOptions.map((server) => (
<SelectItem key={server.value} value={server.value}>
{server.label} {server.emoji}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
{!hasChannels && !isLoadingChannels ? (

View File

@@ -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<HTMLVideoElement>(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;
};

View File

@@ -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<MediaMTXRegion, MediaMTXClientEnvs> = {
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',
},
];
}