Skip to content

Commit

Permalink
feat(web): liveman sub-dashboard for specific nodes
Browse files Browse the repository at this point in the history
  • Loading branch information
rocka committed Dec 22, 2024
1 parent 5c14b85 commit 8717800
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 20 deletions.
11 changes: 9 additions & 2 deletions web/liveman/api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import wretch from 'wretch';
import QueryStringAddon from 'wretch/addons/queryString';

import type { Stream } from '../shared/api';
import { type Stream } from '../shared/api';
import { makeAuthorizationMiddleware } from '../shared/authorization-middleware';

const authMiddleware = makeAuthorizationMiddleware();

const w = wretch().middlewares([authMiddleware]);
const w = wretch().addon(QueryStringAddon).middlewares([authMiddleware]);

export const setAuthToken = authMiddleware.setAuthorization;
export const addUnauthorizedCallback = authMiddleware.addUnauthorizedCallback;
Expand All @@ -32,6 +33,12 @@ export function getNodes() {
return w.url('/api/nodes/').get().json<Node[]>();
}

export { type Stream };

export function getStreams(nodes?: string[]) {
return w.url('/api/streams/').query({ nodes }).get().json<Stream[]>();
}

export interface StreamDetail {
[key: string]: Stream;
}
Expand Down
31 changes: 27 additions & 4 deletions web/liveman/liveman.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useRef, useState } from 'preact/hooks';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { Button } from 'react-daisyui';

import { type Stream } from '@/shared/api';
Expand All @@ -14,12 +14,30 @@ import { type IStreamTokenDialog, StreamTokenDialog } from './components/dialog-
export function Liveman() {
const [token, setToken] = useState('');
const [needsAuthorizaiton, setNeedsAuthorization] = useNeedAuthorization(api);

const onLoginSuccess = (t: string) => {
setToken(t);
setNeedsAuthorization(false);
};

const [filterNodes, setFilterNodes] = useState<string[] | undefined>();
useEffect(() => {
const params = new URLSearchParams(location.search);
setFilterNodes(params.getAll('nodes'));
}, [location.search]);
const getStreams = useCallback(async () => {
const streams = await api.getStreams(filterNodes);
return streams.sort((a, b) => a.createdAt - b.createdAt);
}, [filterNodes]);
const getWhxpUrl = (whxp: 'whep' | 'whip', streamId: string) => {
let url = `/${whxp}/${streamId}`;
if (filterNodes && filterNodes.length > 0) {
const params = new URLSearchParams();
filterNodes?.forEach(v => params.append('nodes', v));
url += `?${params.toString()}`;
}
return new URL(url, location.origin).toString();
};

const refStreamTokenDialog = useRef<IStreamTokenDialog>(null);
const renderCreateToken = useCallback((stream: Stream) => {
return (
Expand All @@ -30,8 +48,13 @@ export function Liveman() {
return (
<>
<PageLayout token={token}>
<NodesTable />
<StreamsTable renderExtraActions={renderCreateToken} />
{filterNodes && filterNodes.length > 0 ? null : <NodesTable />}
<StreamsTable
getStreams={getStreams}
getWhepUrl={streamId => getWhxpUrl('whep', streamId)}
getWhipUrl={streamId => getWhxpUrl('whip', streamId)}
renderExtraActions={renderCreateToken}
/>
</PageLayout>
<StreamTokenDialog ref={refStreamTokenDialog} />
<Login
Expand Down
3 changes: 2 additions & 1 deletion web/shared/components/dialog-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useLogger } from '../hooks/use-logger';
import { QRCodeStreamDecoder } from '../qrcode-stream';

interface Props {
getWhepUrl?: (streamId: string) => string;
onStop(): void
}

Expand Down Expand Up @@ -119,7 +120,6 @@ export const PreviewDialog = forwardRef<IPreviewDialog, Props>((props, ref) => {
});
refPeerConnection.current = pc;
const whep = new WHEPClient();
const url = `${location.origin}/whep/${streamId}`;
whep.onOffer = sdp => {
logger.log('http offer sent');
return sdp;
Expand All @@ -130,6 +130,7 @@ export const PreviewDialog = forwardRef<IPreviewDialog, Props>((props, ref) => {
};
refWhepClient.current = whep;
try {
const url = props.getWhepUrl?.(streamId) ?? `${location.origin}/whep/${streamId}`;
await whep.view(pc, url, tokenContext.token);
} catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
setConnState('Error');
Expand Down
3 changes: 2 additions & 1 deletion web/shared/components/dialog-web-stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useLogger } from '../hooks/use-logger';
import { QRCodeStream } from '../qrcode-stream';

interface Props {
getWhipUrl?: (streamId: string) => string;
onStop(): void
}

Expand Down Expand Up @@ -68,7 +69,6 @@ export const WebStreamDialog = forwardRef<IWebStreamDialog, Props>((props, ref)
});
const whip = new WHIPClient();
refWhipClient.current = whip;
const url = `${location.origin}/whip/${streamId}`;
whip.onOffer = sdp => {
logger.log('http offer sent');
return sdp;
Expand All @@ -78,6 +78,7 @@ export const WebStreamDialog = forwardRef<IWebStreamDialog, Props>((props, ref)
return sdp;
};
try {
const url = props.getWhipUrl?.(streamId) ?? `${location.origin}/whep/${streamId}`;
await whip.publish(pc, url, tokenContext.token);
} catch (e: any) { // eslint-disable-line @typescript-eslint/no-explicit-any
setConnState('Error');
Expand Down
7 changes: 6 additions & 1 deletion web/shared/components/streams-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ async function getStreamsSorted() {
}

export interface StreamTableProps {
getStreams?: () => Promise<Stream[]>;
getWhepUrl?: (streamId: string) => string;
getWhipUrl?: (streamId: string) => string;
showCascade?: boolean;
renderExtraActions?: (s: Stream) => ReactNode;
}

export function StreamsTable(props: StreamTableProps) {
const streams = useRefreshTimer([], getStreamsSorted);
const streams = useRefreshTimer([], props.getStreams ?? getStreamsSorted);
const [selectedStreamId, setSelectedStreamId] = useState('');
const refCascadePull = useRef<ICascadeDialog>(null);
const refCascadePush = useRef<ICascadeDialog>(null);
Expand Down Expand Up @@ -227,6 +230,7 @@ export function StreamsTable(props: StreamTableProps) {
refPreviewStreams.current.delete(s);
}
}}
getWhepUrl={props.getWhepUrl}
onStop={() => handlePreviewStop(s)}
/>
)}
Expand All @@ -243,6 +247,7 @@ export function StreamsTable(props: StreamTableProps) {
refWebStreams.current.delete(s);
}
}}
getWhipUrl={props.getWhipUrl}
onStop={() => handleWebStreamStop(s)}
/>
)}
Expand Down
18 changes: 7 additions & 11 deletions web/shared/hooks/use-refresh-timer.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import { useEffect, useState } from 'preact/hooks';
import { useCallback, useEffect, useState } from 'preact/hooks';

export function useRefreshTimer<T>(initial: T, fetchData: () => Promise<T>, timeout = 3000, immediate = true) {
const [data, setData] = useState<T>(initial);
const [isImmediate, _setIsImmediate] = useState(immediate);
const [refreshTimer, setRefreshTimer] = useState(-1);
const isRefreshing = refreshTimer > 0;
const updateData = async () => {
setData(await fetchData());
};
const updateData = useCallback(async () => setData(await fetchData()), [fetchData]);
useEffect(() => {
if (isImmediate) {
updateData();
}
return () => {
if (isRefreshing) {
window.clearInterval(refreshTimer);
}
};
}, []);
useEffect(() => {
if (isRefreshing) {
clearInterval(refreshTimer);
window.clearInterval(refreshTimer);
setRefreshTimer(window.setInterval(updateData, timeout));
}
}, [timeout]);
return () => {
window.clearInterval(refreshTimer);
};
}, [updateData, timeout]);
const toggleTimer = () => {
if (isRefreshing) {
clearInterval(refreshTimer);
Expand Down

0 comments on commit 8717800

Please sign in to comment.