Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { ManageProductionsPage } from "./components/manage-productions-page/mana
import { CreateProductionPage } from "./components/create-production/create-production-page.tsx";
import { useSetupTokenRefresh } from "./hooks/use-reauth.tsx";
import { TUserSettings } from "./components/user-settings/types";
import { IOBridgePage } from "./components/io-bridge-page/io-bridge-page.tsx";

const DisplayBoxPositioningContainer = styled(FlexContainer)`
justify-content: center;
Expand Down Expand Up @@ -152,6 +153,13 @@ const AppContent = ({
}
errorElement={<ErrorPage />}
/>
<Route
path="/manage-io-bridge"
element={
<IOBridgePage setApiError={() => setApiError(true)} />
}
errorElement={<ErrorPage />}
/>
<Route
path="/production-calls/production/:productionId/line/:lineId"
element={<CallsPage />}
Expand Down
243 changes: 240 additions & 3 deletions src/api/api.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { handleFetchRequest } from "./handle-fetch-request.ts";

const API_VERSION = import.meta.env.VITE_BACKEND_API_VERSION ?? "api/v1/";
const API_URL =
`${import.meta.env.VITE_BACKEND_URL.replace(/\/+$/, "")}/${API_VERSION}` ||
`${window.location.origin}/${API_VERSION}`;
const API_URL = import.meta.env.VITE_BACKEND_URL
? `${import.meta.env.VITE_BACKEND_URL.replace(/\/+$/, "")}/${API_VERSION}`
: `${window.location.origin}/${API_VERSION}`;
const API_KEY = import.meta.env.VITE_BACKEND_API_KEY;

// Helper to get backend base URL for constructing WHIP/WHEP URLs
export const getBackendBaseUrl = (): string => {
return import.meta.env.VITE_BACKEND_URL
? import.meta.env.VITE_BACKEND_URL.replace(/\/+$/, "")
: window.location.origin;
};

type TCreateProductionOptions = {
name: string;
lines: { name: string; programOutputLine?: boolean }[];
Expand Down Expand Up @@ -33,6 +40,74 @@ export type TBasicProductionResponse = {
lines: TLine[];
};

export type TSavedTransmitter = {
_id: string;
label?: string;
port: number;
productionId: number;
lineId: number;
whipUrl: string;
passThroughUrl?: string;
mode: "caller" | "listener";
srtUrl?: string;
status: "idle" | "running" | "stopped" | "failed";
createdAt?: string;
updatedAt?: string;
};

export type TSavedReceiver = {
_id: string;
label?: string;
productionId: number;
lineId: number;
whepUrl: string;
srtUrl: string;
status: "idle" | "running" | "stopped" | "failed";
createdAt?: string;
updatedAt?: string;
};

export enum TSrtMode {
CALLER = "caller",
LISTENER = "listener",
}

export enum TBridgeState {
IDLE = "idle",
RUNNING = "running",
STOPPED = "stopped",
FAILED = "failed",
}

export type TEditTransmitter = {
id: string;
state: TBridgeState;
};

export type TEditReceiver = {
id: string;
state: TBridgeState;
};

export type TPatchTransmitter = {
id: string;
label?: string;
productionId?: number;
lineId?: number;
};

export type TPatchReceiver = {
id: string;
label?: string;
productionId?: number;
lineId?: number;
};

export type TBridgeConfig = {
whipGatewayEnabled: boolean;
whepGatewayEnabled: boolean;
};

export type TListProductionsResponse = {
productions: TBasicProductionResponse[];
offset: 0;
Expand Down Expand Up @@ -292,4 +367,166 @@ export const API = {
})
);
},

// Bridge configuration
fetchBridgeConfig: (): Promise<TBridgeConfig> =>
handleFetchRequest<TBridgeConfig>(
fetch(`${API_URL}bridge/config`, {
method: "GET",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
),

// Transmitter (Bridge TX) endpoints - via intercom-manager
createTransmitter: async (data: {
label?: string;
port: number;
productionId: number;
lineId: number;
whipUrl: string;
passThroughUrl?: string;
mode: "caller" | "listener";
srtUrl?: string;
}) =>
handleFetchRequest<TSavedTransmitter>(
fetch(`${API_URL}bridge/tx`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify(data),
})
),
fetchTransmitterList: (): Promise<TSavedTransmitter[]> =>
handleFetchRequest<{ transmitters: TSavedTransmitter[] }>(
fetch(`${API_URL}bridge/tx`, {
method: "GET",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
).then((res) => res?.transmitters || []),
fetchTransmitter: (id: string): Promise<TSavedTransmitter> =>
handleFetchRequest<TSavedTransmitter>(
fetch(`${API_URL}bridge/tx/${id}`, {
method: "GET",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
),
updateTransmitterState: async (data: TEditTransmitter) =>
handleFetchRequest<TSavedTransmitter>(
fetch(`${API_URL}bridge/tx/${data.id}/state`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({
desired: data.state,
}),
})
),
updateTransmitter: async (data: TPatchTransmitter) =>
handleFetchRequest<TSavedTransmitter>(
fetch(`${API_URL}bridge/tx/${data.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({
label: data.label,
productionId: data.productionId,
lineId: data.lineId,
}),
})
),
deleteTransmitter: async (id: string): Promise<string> =>
handleFetchRequest<string>(
fetch(`${API_URL}bridge/tx/${id}`, {
method: "DELETE",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
),

// Receiver (Bridge RX) endpoints - via intercom-manager
createReceiver: async (data: {
label?: string;
productionId: number;
lineId: number;
whepUrl: string;
srtUrl: string;
}) =>
handleFetchRequest<TSavedReceiver>(
fetch(`${API_URL}bridge/rx`, {
method: "POST",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify(data),
})
),
fetchReceiverList: (): Promise<TSavedReceiver[]> =>
handleFetchRequest<{ receivers: TSavedReceiver[] }>(
fetch(`${API_URL}bridge/rx`, {
method: "GET",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
).then((res) => res?.receivers || []),
fetchReceiver: (id: string): Promise<TSavedReceiver> =>
handleFetchRequest<TSavedReceiver>(
fetch(`${API_URL}bridge/rx/${id}`, {
method: "GET",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
),
updateReceiverState: async (data: TEditReceiver) =>
handleFetchRequest<TSavedReceiver>(
fetch(`${API_URL}bridge/rx/${data.id}/state`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({
desired: data.state,
}),
})
),
updateReceiver: async (data: TPatchReceiver) =>
handleFetchRequest<TSavedReceiver>(
fetch(`${API_URL}bridge/rx/${data.id}`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({
label: data.label,
productionId: data.productionId,
lineId: data.lineId,
}),
})
),
deleteReceiver: async (id: string): Promise<string> =>
handleFetchRequest<string>(
fetch(`${API_URL}bridge/rx/${id}`, {
method: "DELETE",
headers: {
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
})
),
};
6 changes: 6 additions & 0 deletions src/assets/icons/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import VolumeOff from "./volume_off.svg?react";
import VolumeOn from "./volume_on.svg?react";
import Warning from "./warning.svg?react";
import WhipSvg from "./whip_user.svg?react";
import Stop from "./stop.svg?react";
import Play from "./play.svg?react";

export const MicMuted = () => <MicMute />;

Expand Down Expand Up @@ -79,3 +81,7 @@ export const SaveIcon = () => <Save />;
export const HelpIcon = () => <Help />;

export const WarningIcon = () => <Warning />;

export const StopIcon = () => <Stop />;

export const PlayIcon = () => <Play />;
3 changes: 3 additions & 0 deletions src/assets/icons/play.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions src/assets/icons/stop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions src/components/delete-button/delete-button-components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export const ButtonsWrapper = styled.div`
display: flex;
justify-content: flex-end;
margin: 1rem 0 1rem 0;
gap: 1rem;
`;

export const DeleteButton = styled(SecondaryButton)`
Expand Down
Loading