Skip to content
Merged
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
2 changes: 1 addition & 1 deletion tauri/src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"productName": "hopp",
"mainBinaryName": "hopp",
"version": "0.0.38",
"version": "0.0.39",
"identifier": "com.hopp.app",
"plugins": {
"updater": {
Expand Down
2 changes: 1 addition & 1 deletion tauri/src/components/ui/call-banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ export const CallBanner = ({ callerId, toastId }: { callerId: string; toastId: s
...data.payload,
timeStarted: new Date(),
hasAudioEnabled: true,
hasCameraEnabled: false,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
cameraTrackId: null,
});

toast.dismiss(toastId);
Expand Down
27 changes: 17 additions & 10 deletions tauri/src/components/ui/call-center.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -498,11 +498,11 @@ function ScreensharingEventListener({

function CameraIcon() {
const { updateCallTokens, callTokens } = useStore();
const hasCameraEnabled = callTokens?.hasCameraEnabled || false;
const [retry, setRetry] = useState(0);

const tracks = useTracks([Track.Source.Camera], {});
const [cameraEnabled, setCameraEnabled] = useState(callTokens?.cameraTrackId !== null);

const tracks = useTracks([Track.Source.Camera], {});
const [roomConnected, setRoomConnected] = useState(false);
const room = useRoomContext();
useEffect(() => {
Expand Down Expand Up @@ -569,14 +569,12 @@ function CameraIcon() {

useEffect(() => {
if (roomConnected) {
pubUnpubTrack(hasCameraEnabled);
pubUnpubTrack(cameraEnabled);
}
}, [hasCameraEnabled, localParticipant, roomConnected]);
}, [cameraEnabled, localParticipant, roomConnected]);

const handleCameraToggle = () => {
updateCallTokens({
hasCameraEnabled: !hasCameraEnabled,
});
setCameraEnabled(!cameraEnabled);
};

const handleCameraChange = (value: string) => {
Expand All @@ -591,22 +589,31 @@ function CameraIcon() {
};

useEffect(() => {
console.log("tracks", tracks);
if (tracks.length > 0) {
tauriUtils.ensureCameraWindowIsVisible(callTokens?.cameraToken || "");
} else {
// If there are 0 then close the window
tauriUtils.closeCameraWindow();
}

if (localParticipant) {
for (const track of localParticipant.getTrackPublications()) {
if (track.source === Track.Source.Camera) {
updateCallTokens({
cameraTrackId: track.trackSid,
});
}
}
}
}, [tracks]);

const isDisabled = cameraDevices.length === 0;
return (
<ToggleIconButton
onClick={handleCameraToggle}
icon={hasCameraEnabled ? <LuVideo className="size-4" /> : <LuVideoOff className="size-4" />}
icon={cameraEnabled ? <LuVideo className="size-4" /> : <LuVideoOff className="size-4" />}
state={
hasCameraEnabled ? "active"
cameraEnabled ? "active"
: isDisabled ?
"deactivated"
: "neutral"
Expand Down
2 changes: 1 addition & 1 deletion tauri/src/components/ui/participant-row-wo-livekit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ export const ParticipantRow = (props: { user: components["schemas"]["BaseUser"]
...data.payload,
timeStarted: new Date(),
hasAudioEnabled: true,
hasCameraEnabled: false,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
cameraTrackId: null,
});
break;
}
Expand Down
4 changes: 2 additions & 2 deletions tauri/src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ export enum ParticipantRole {
SHARER = "sharer",
CONTROLLER = "controller",
NONE = "none",
};
}

export type CallState = {
timeStarted: Date;
hasAudioEnabled: boolean;
hasCameraEnabled?: boolean;
// Managing buttons for starting/joining/terminating screenshare streams
role: ParticipantRole;
isRemoteControlEnabled: boolean;
isRoomCall?: boolean;
cameraTrackId?: string | null;
} & TCallTokensMessage["payload"];

type State = {
Expand Down
115 changes: 97 additions & 18 deletions tauri/src/windows/camera/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,34 @@ import { Track } from "livekit-client";
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
import { PhysicalSize, LogicalPosition, currentMonitor } from "@tauri-apps/api/window";
import { CgSpinner } from "react-icons/cg";
import { HiOutlineEye, HiOutlineEyeSlash } from "react-icons/hi2";
import { RiExpandDiagonalLine, RiCollapseDiagonalLine } from "react-icons/ri";
import { WindowActions } from "@/components/ui/window-buttons";
import { CustomIcons } from "@/components/ui/icons";
import { Button } from "@/components/ui/button";
import useStore from "@/store/store";
import clsx from "clsx";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<CameraWindow />
</React.StrictMode>,
);

async function CameraWindowSize({ numOfTracks }: { numOfTracks: number }) {
const EXPANSION_FACTOR = 1.3;

async function CameraWindowSize({
numOfTracks,
expansionFactor = 1,
}: {
numOfTracks: number;
expansionFactor?: number;
}) {
let trackLength = numOfTracks;

// All values are in pixels
const FlexGap = 4; // 0.25rem
const VideoCardHeight = 140;
const VideoCardHeight = 140 * expansionFactor;
const HeaderHeight = 36;
const VideoCardPadding = 14; // 0.875rem

Expand All @@ -38,19 +51,42 @@ async function CameraWindowSize({ numOfTracks }: { numOfTracks: number }) {

const appWindow = getCurrentWebviewWindow();
const factor = await appWindow.scaleFactor();
appWindow.setSize(new PhysicalSize(160 * factor, totalHeight * factor));
console.log(
`Tracks ${trackLength}`,
`Expansion factor ${expansionFactor}`,
`Factor ${factor}`,
`Total height ${totalHeight}`,
);
appWindow.setSize(new PhysicalSize(Math.floor(160 * expansionFactor * factor), Math.floor(totalHeight * factor)));
}

function ConsumerComponent() {
function ConsumerComponent({
hideSelf,
setHideSelf,
isExpanded,
}: {
hideSelf: boolean;
setHideSelf: (value: boolean) => void;
isExpanded: boolean;
}) {
const { callTokens } = useStore();

const tracks = useTracks([Track.Source.Camera], {
onlySubscribed: true,
});

const visibleTracks = tracks.filter((track) => {
const isSelfTrack = callTokens?.cameraTrackId === track?.publication?.trackSid;
return !(hideSelf && isSelfTrack);
});

useEffect(() => {
console.log("tracks ", tracks);
// Set window size appropriately
CameraWindowSize({ numOfTracks: tracks.length });
}, [tracks]);
CameraWindowSize({ numOfTracks: visibleTracks.length, expansionFactor: isExpanded ? EXPANSION_FACTOR : 1 });
}, [visibleTracks, isExpanded]);

const factor = isExpanded ? EXPANSION_FACTOR : 1;

return (
<div className="content px-2 py-4">
Expand All @@ -72,26 +108,39 @@ function ConsumerComponent() {
<span className="text-sm text-white/80">Loading</span>
</div>
)}
{tracks.map((track) => {
{visibleTracks.map((track) => {
return (
<div className="overflow-hidden rounded-lg" key={track.sid}>
<div className="relative overflow-hidden rounded-lg group" key={track.sid}>
<VideoTrack
trackRef={track}
className="rounded-lg object-cover overflow-hidden"
style={{
aspectRatio: "1/1",
width: "140px",
height: "140px",
minHeight: "140px",
minWidth: "140px",
maxHeight: "140px",
maxWidth: "140px",
width: `${Math.floor(140 * factor)}px`,
height: `${Math.floor(140 * factor)}px`,
minHeight: `${Math.floor(140 * factor)}px`,
minWidth: `${Math.floor(140 * factor)}px`,
maxHeight: `${Math.floor(140 * factor)}px`,
maxWidth: `${Math.floor(140 * factor)}px`,
border:
track?.participant?.isSpeaking ?
"1px solid rgba(157, 253, 49, 0.8)"
: "1px solid rgba(0, 0, 0, 0.1)",
}}
/>
{callTokens?.cameraTrackId === track?.publication?.trackSid && (
<div className="absolute inset-0 bg-black/50 opacity-0 group-hover:opacity-100 transition-opacity duration-200 flex items-center justify-center rounded-lg">
<Button
variant="secondary"
size="icon-sm"
className="bg-white/20 hover:bg-white/30 text-white border-white/20"
title="Hide participant"
onClick={() => setHideSelf(true)}
>
<HiOutlineEyeSlash className="w-4 h-4" />
</Button>
</div>
)}
</div>
);
})}
Expand Down Expand Up @@ -139,12 +188,13 @@ const putWindowCorner = async () => {
function CameraWindow() {
useDisableNativeContextMenu();
const [cameraToken, setCameraToken] = useState<string | null>(null);

const [isSelfHidden, setIsSelfHidden] = useState(false);
const [livekitUrl, setLivekitUrl] = useState<string>("");
const [isExpanded, setIsExpanded] = useState(false);

useEffect(() => {
// Set correct window size
CameraWindowSize({ numOfTracks: 0 });
CameraWindowSize({ numOfTracks: 0, expansionFactor: isExpanded ? EXPANSION_FACTOR : 1 });

const cameraTokenFromUrl = tauriUtils.getTokenParam("cameraToken");

Expand Down Expand Up @@ -174,12 +224,41 @@ function CameraWindow() {
<WindowActions.Empty onClick={() => putWindowCorner()} className=" justify-self-start">
<CustomIcons.Corner />
</WindowActions.Empty>
<CustomIcons.Drag className="absolute left-1/2 -translate-x-1/2 pointer-events-none" />
<CustomIcons.Drag
className={clsx(
"absolute left-1/2 -translate-x-1/2 pointer-events-none",
isSelfHidden ? "left-[33%] -translate-x-[33%]" : "left-1/2 -translate-x-1/2",
)}
/>
{/* <div className="pointer-events-none ml-auto font-medium text-white/80 text-[12px]">+2 more users</div> */}
<div className="ml-auto flex items-center gap-1">
<Button
variant="ghost"
size="icon-sm"
className="text-white/80 hover:text-white hover:bg-white/10"
onClick={() => setIsExpanded(!isExpanded)}
title={isExpanded ? "Collapse window" : "Expand window"}
>
{isExpanded ?
<RiCollapseDiagonalLine className="size-4" />
: <RiExpandDiagonalLine className="size-4" />}
</Button>
{isSelfHidden && (
<Button
variant="ghost"
size="icon-sm"
className="text-white/80 hover:text-white hover:bg-white/10"
onClick={() => setIsSelfHidden(false)}
title="Show self"
>
<HiOutlineEye className="w-4 h-4" />
</Button>
)}
</div>
</div>
<Toaster position="bottom-center" />
<LiveKitRoom token={cameraToken ?? undefined} serverUrl={livekitUrl}>
<ConsumerComponent />
<ConsumerComponent hideSelf={isSelfHidden} setHideSelf={setIsSelfHidden} isExpanded={isExpanded} />
</LiveKitRoom>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions tauri/src/windows/main-window/tabs/Debug.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const Debug = () => {
hasAudioEnabled: false,
hasVideoEnabled: false,
isRemoteControlEnabled: true,
cameraTrackId: null,
});
}}
/>
Expand Down
1 change: 1 addition & 0 deletions tauri/src/windows/main-window/tabs/Rooms.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export const Rooms = () => {
hasAudioEnabled: true,
role: ParticipantRole.NONE,
isRemoteControlEnabled: true,
cameraTrackId: null,
});
} catch (error) {
toast.error("Error joining watercooler room");
Expand Down
Loading