Skip to content
Open
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: 7 additions & 1 deletion e2e/tests/create-production.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,14 @@ test.describe("Create Production", () => {
await createProductionPage.goto();
await createProductionPage.addLineButton.click();
// Both lines should have Audio Feed checkboxes
await expect(createProductionPage.page.getByText("Audio Feed")).toHaveCount(
2
);
await expect(
createProductionPage.page.getByText("Video Enabled")
).toHaveCount(2);
const checkboxes = createProductionPage.page.getByRole("checkbox");
await expect(checkboxes).toHaveCount(2);
await expect(checkboxes).toHaveCount(4);
});

test("shows line numbers on cards", async ({ createProductionPage }) => {
Expand Down
58 changes: 56 additions & 2 deletions src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ export type TPreset = {

type TCreateProductionOptions = {
name: string;
lines: { name: string; programOutputLine?: boolean }[];
lines: {
name: string;
programOutputLine?: boolean;
videoEnabled?: boolean;
}[];
};

type TParticipant = {
Expand All @@ -33,6 +37,8 @@ type TParticipant = {
endpointId: string;
isActive: boolean;
isWhip: boolean;
isWhepReceiver?: boolean;
hasVideo: boolean;
};

type TLine = {
Expand All @@ -41,6 +47,8 @@ type TLine = {
smbConferenceId: string;
participants: TParticipant[];
programOutputLine?: boolean;
videoEnabled?: boolean;
whepSourceSessionId?: string | null;
};

export type TBasicProductionResponse = {
Expand Down Expand Up @@ -155,6 +163,47 @@ export const API = {
}),
})
),

setLineWhepSource: async ({
productionId,
lineId,
sessionId,
}: {
productionId: string;
lineId: string;
sessionId: string | null;
}): Promise<{ lineId: string; pinnedSessionId: string | null }> =>
handleFetchRequest<{ lineId: string; pinnedSessionId: string | null }>(
fetch(`${API_URL}production/${productionId}/line/${lineId}/whep-source`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({ pinnedSessionId: sessionId }),
})
),

setSessionVideoSource: async ({
sessionId,
pinnedSessionId,
}: {
sessionId: string;
pinnedSessionId: string | null;
}): Promise<{ sessionId: string; pinnedSessionId: string | null }> =>
handleFetchRequest<{
sessionId: string;
pinnedSessionId: string | null;
}>(
fetch(`${API_URL}session/${sessionId}/video-source`, {
method: "PATCH",
headers: {
"Content-Type": "application/json",
...(API_KEY ? { Authorization: `Bearer ${API_KEY}` } : {}),
},
body: JSON.stringify({ pinnedSessionId }),
})
),
listProductions: ({
searchParams,
}: {
Expand Down Expand Up @@ -227,7 +276,8 @@ export const API = {
addProductionLine: (
productionId: string,
name: string,
programOutputLine?: boolean
programOutputLine?: boolean,
videoEnabled?: boolean
): Promise<TLine> =>
handleFetchRequest<TLine>(
fetch(`${API_URL}production/${productionId}/line`, {
Expand All @@ -239,6 +289,7 @@ export const API = {
body: JSON.stringify({
name,
programOutputLine,
videoEnabled,
}),
})
),
Expand Down Expand Up @@ -369,6 +420,7 @@ export const API = {
body: JSON.stringify(options),
})
),

listPresets: (): Promise<{ presets: TPreset[] }> =>
handleFetchRequest<{ presets: TPreset[] }>(
fetch(`${API_URL}preset`, {
Expand All @@ -378,6 +430,7 @@ export const API = {
},
})
),

deletePreset: async (id: string): Promise<void> => {
const response = await fetch(`${API_URL}preset/${id}`, {
method: "DELETE",
Expand All @@ -389,6 +442,7 @@ export const API = {
await handleFetchRequest<void>(Promise.resolve(response));
}
},

updatePreset: (
id: string,
update: {
Expand Down
1 change: 1 addition & 0 deletions src/assets/icons/delete.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/assets/icons/fullscreen.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/assets/icons/fullscreen_exit.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions src/assets/icons/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ 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 DeleteSvg from "./delete.svg?react";
import VideoOn from "./video_on.svg?react";
import VideoOff from "./video_off.svg?react";
import Pin from "./pin.svg?react";
import Unpin from "./unpin.svg?react";
import Fullscreen from "./fullscreen.svg?react";
import FullscreenExit from "./fullscreen_exit.svg?react";

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

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

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

export const DeleteIcon = () => <DeleteSvg />;

export const VideoOnIcon = () => <VideoOn />;

export const VideoOffIcon = () => <VideoOff />;

export const PinIcon = () => <Pin />;

export const UnpinIcon = () => <Unpin />;

export const FullscreenIcon = () => <Fullscreen />;

export const FullscreenExitIcon = () => <FullscreenExit />;
1 change: 1 addition & 0 deletions src/assets/icons/pin.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/assets/icons/unpin.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/assets/icons/video_off.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/assets/icons/video_on.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions src/components/calls-page/calls-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import { ConfirmationModal } from "../verify-decision/confirmation-modal";
import { HeaderActions } from "./header-actions";
import { ProductionLines } from "./production-lines";
import { ProgramLineJoinCard } from "./program-line-join-card";
import { VideoLineJoinCard } from "./video-line-join-card";
import { useCallsNavigation } from "./use-calls-navigation";
import { useGlobalMuteHotkey } from "./use-global-mute-hotkey";
import { usePreventPullToRefresh } from "./use-prevent-pull-to-refresh";
Expand Down Expand Up @@ -137,6 +138,16 @@ export const CallsPage = () => {
const [pendingProgramLines, setPendingProgramLines] = useState<
PendingProgramLine[]
>([]);
type PendingVideoLine = {
productionId: string;
lineId: string;
lineName?: string;
productionName?: string;
};

const [pendingVideoRefs, setPendingVideoRefs] = useState<PendingVideoLine[]>(
[]
);
const {
deregisterCall,
registerCallList,
Expand All @@ -154,6 +165,7 @@ export const CallsPage = () => {
const { initiateProductionCall } = useInitiateProductionCall({ dispatch });
const autoJoinTriggeredRef = useRef(false);
const pendingProgramKeysRef = useRef<Set<string>>(new Set());
const pendingVideoKeysRef = useRef<Set<string>>(new Set());

const { productionId: paramProductionId, lineId: paramLineId } = useParams();
const { search } = useLocation();
Expand Down Expand Up @@ -277,6 +289,7 @@ export const CallsPage = () => {
);

const program: PendingProgramLine[] = [];
const video: PendingVideoLine[] = [];
const validRefs: typeof pendingCallRefs = [];

pendingCallRefs.forEach((ref, i) => {
Expand All @@ -294,6 +307,13 @@ export const CallsPage = () => {
productionName: prod.name,
lineName: line.name,
});
} else if (line.videoEnabled === true) {
video.push({
productionId: ref.productionId,
lineId: ref.lineId,
productionName: prod.name,
lineName: line.name,
});
}
});

Expand All @@ -304,6 +324,13 @@ export const CallsPage = () => {
setPendingProgramLines(program);
}

if (video.length > 0) {
pendingVideoKeysRef.current = new Set(
video.map((r) => `${r.productionId}:${r.lineId}`)
);
setPendingVideoRefs(video);
}

if (validRefs.length !== pendingCallRefs.length) {
navigate(buildCallsUrl(validRefs, autoCompanionUrl), {
replace: true,
Expand All @@ -330,6 +357,7 @@ export const CallsPage = () => {
autoJoinTriggeredRef.current = true;

const programKeys = pendingProgramKeysRef.current;
const videoKeys = pendingVideoKeysRef.current;

const existingKeys = new Set(
Object.values(calls)
Expand All @@ -342,6 +370,7 @@ export const CallsPage = () => {
.filter(
(ref) =>
!programKeys.has(`${ref.productionId}:${ref.lineId}`) &&
!videoKeys.has(`${ref.productionId}:${ref.lineId}`) &&
!existingKeys.has(`${ref.productionId}:${ref.lineId}`)
)
.forEach((ref) => {
Expand Down Expand Up @@ -553,6 +582,42 @@ export const CallsPage = () => {
/>
)}
<CallsContainer>
{!!(
userSettings &&
userSettings.username &&
(userSettings.audioinput || userSettings.audiooutput)
) &&
pendingVideoRefs
.filter(
(ref) =>
!Object.values(calls).some(
(c) =>
c.joinProductionOptions?.productionId ===
ref.productionId &&
c.joinProductionOptions?.lineId === ref.lineId
)
)
.map((ref) => (
<VideoLineJoinCard
key={`${ref.productionId}:${ref.lineId}`}
productionId={ref.productionId}
lineId={ref.lineId}
lineName={ref.lineName}
productionName={ref.productionName}
customGlobalMute={customGlobalMute}
onJoined={() =>
setPendingVideoRefs((prev) =>
prev.filter(
(r) =>
!(
r.productionId === ref.productionId &&
r.lineId === ref.lineId
)
)
)
}
/>
))}
{addCallActive &&
!isGuest &&
(productionId || addCallPreSelected) && (
Expand Down
Loading
Loading