(fix): joining a call while already being in a call wouldn't reset the call state

(qol): format
This commit is contained in:
Alois 2026-04-30 23:37:26 +02:00
commit 4dbf48b3f9
8 changed files with 85 additions and 50 deletions

View file

@ -1,10 +1,5 @@
import { useTTP } from "@tensamin/ttp";
import {
Button,
Popover,
PopoverContent,
PopoverTrigger,
} from "@tensamin/ui";
import { Button, Popover, PopoverContent, PopoverTrigger } from "@tensamin/ui";
import Wrapper from "@tensamin/user/wrapper";
import { Mail } from "lucide-react";
import { sendCallInvite } from "../../store";

View file

@ -142,7 +142,8 @@ export default function Base({
{/* Detect video / user and place here */}
{type === "stream" &&
(screenSharePublication?.isSubscribed && screenSharePublication.track ? (
(screenSharePublication?.isSubscribed &&
screenSharePublication.track ? (
<VideoViewer
flush={flush}
participantId={participant.identity}

View file

@ -7,12 +7,15 @@ export default function TopBar() {
const room = useCall((state) => state.room);
const view = useCall((state) => state.view);
const userIds = Array.from(room.remoteParticipants.values(), (participant) => {
const participantId = Number(participant.identity);
return Number.isInteger(participantId) && participantId > 0
? participantId
: null;
}).filter((participantId): participantId is number => participantId != null);
const userIds = Array.from(
room.remoteParticipants.values(),
(participant) => {
const participantId = Number(participant.identity);
return Number.isInteger(participantId) && participantId > 0
? participantId
: null;
},
).filter((participantId): participantId is number => participantId != null);
const userIdsKey = userIds.join(",");
const [users, setUsers] = useState<User[]>([]);

View file

@ -51,7 +51,13 @@ export function createScreenShareController({
await Promise.all(
screenShareSession.tracks.map((track) =>
room.localParticipant.unpublishTrack(track, true).catch((error) => {
log(1, "call", "red", "Failed to unpublish screen share track", error);
log(
1,
"call",
"red",
"Failed to unpublish screen share track",
error,
);
}),
),
);
@ -194,15 +200,12 @@ export function createScreenShareController({
});
}, 125);
await publishScreenShareTracks(
[videoTrack],
() => {
stopped = true;
window.clearInterval(interval);
stream.getTracks().forEach((track) => track.stop());
canvas.remove();
},
);
await publishScreenShareTracks([videoTrack], () => {
stopped = true;
window.clearInterval(interval);
stream.getTracks().forEach((track) => track.stop());
canvas.remove();
});
}
async function stopScreenShare() {

View file

@ -157,7 +157,8 @@ function getActiveScreenShareParticipantIds(): number[] {
.map((participant) => ({
participantId: getParticipantId(participant.identity),
hasScreenShare:
participant.getTrackPublication(Track.Source.ScreenShare)?.track != null,
participant.getTrackPublication(Track.Source.ScreenShare)?.track !=
null,
}))
.filter(
(entry): entry is { participantId: number; hasScreenShare: true } =>
@ -290,18 +291,21 @@ export async function sendCallInvite(userId: number) {
const ownUserId = (await runtime.load("user_id")) as number;
const privateKey = await runtime.load("private_key");
const ownPublicKey = await runtime.getUser(ownUserId).then(
(data) => data.public_key,
);
const remotePublicKey = await runtime.getUser(userId).then(
(data) => data.public_key,
);
const ownPublicKey = await runtime
.getUser(ownUserId)
.then((data) => data.public_key);
const remotePublicKey = await runtime
.getUser(userId)
.then((data) => data.public_key);
const sharedSecret = await runtime.getSharedSecret(
privateKey,
ownPublicKey,
remotePublicKey,
);
const encryptedCallSecret = await runtime.encryptText(sharedSecret, callSecret);
const encryptedCallSecret = await runtime.encryptText(
sharedSecret,
callSecret,
);
await runtime.send("call_invite", {
receiver_id: userId,
@ -373,8 +377,9 @@ export function stopWatchingFocusedStream() {
stopWatchingStream(focusedParticipantId);
}
let screenShareController: ReturnType<typeof createScreenShareController> | null =
null;
let screenShareController: ReturnType<
typeof createScreenShareController
> | null = null;
function getScreenShareController() {
if (!screenShareController) {
@ -390,7 +395,8 @@ function getScreenShareController() {
: updater,
);
},
getLocalParticipantId: () => getParticipantId(room.localParticipant.identity),
getLocalParticipantId: () =>
getParticipantId(room.localParticipant.identity),
startWatching: startWatchingStream,
stopWatching: stopWatchingStream,
syncParticipantState,
@ -439,7 +445,13 @@ export async function disconnect() {
try {
await getScreenShareController().clearPublishedScreenShare();
} catch (error) {
log(1, "call", "red", "Failed to clear screen share during disconnect", error);
log(
1,
"call",
"red",
"Failed to clear screen share during disconnect",
error,
);
}
useCall.setState({
@ -463,7 +475,7 @@ export async function disconnect() {
});
try {
room.disconnect();
await room.disconnect();
} catch (error) {
log(1, "call", "red", "Failed to disconnect from room", error);
} finally {
@ -480,6 +492,11 @@ export async function joinCall(
sendInvite = true,
) {
const runtime = requireRuntime(useCall.getState().runtime);
const state = useCall.getState().state;
if (state !== "closed") {
await disconnect();
}
log(2, "call", "purple", "Call creation initialised");
useCall.setState({
@ -754,11 +771,10 @@ export function useInitializeCall() {
if (invitedUserId != null) {
setTimeout(async () => {
void sendCallInvite(invitedUserId)
.catch((error) => {
toast("error", "Failed to send call invite.");
log(1, "call", "red", "Failed to send call invite", error);
});
void sendCallInvite(invitedUserId).catch((error) => {
toast("error", "Failed to send call invite.");
log(1, "call", "red", "Failed to send call invite", error);
});
}, 1000);
}

View file

@ -15,7 +15,10 @@ export default function View() {
(state) => state.activeScreenShareParticipantIds,
);
const containerRef = useRef<HTMLDivElement | null>(null);
const [focusedTileSize, setFocusedTileSize] = useState({ width: 0, height: 0 });
const [focusedTileSize, setFocusedTileSize] = useState({
width: 0,
height: 0,
});
const [isFocusedTileFlush, setIsFocusedTileFlush] = useState(false);
const [participantVersion, setParticipantVersion] = useState(0);
@ -42,7 +45,7 @@ export default function View() {
const participantId = Number(participant.identity);
return Number.isInteger(participantId) && participantId > 0;
});
// eslint-disable-next-line
// eslint-disable-next-line
}, [participantVersion, room]);
const userIds = useMemo(
@ -141,7 +144,10 @@ export default function View() {
activeScreenShareParticipantIdSet.has(focusedParticipantId);
return (
<div ref={containerRef} className="flex h-full w-full items-center justify-center overflow-hidden">
<div
ref={containerRef}
className="flex h-full w-full items-center justify-center overflow-hidden"
>
<div
className="flex max-h-full w-full flex-col items-center overflow-hidden"
style={{ gap: tiles.length > 0 ? STACK_GAP_PX : 0 }}

View file

@ -129,13 +129,16 @@ export default function View() {
}, [room]);
const users = useMemo(() => {
const participants = [...room.remoteParticipants.values(), room.localParticipant];
const participants = [
...room.remoteParticipants.values(),
room.localParticipant,
];
return participants.filter((participant) => {
const participantId = Number(participant.identity);
return Number.isInteger(participantId) && participantId > 0;
});
// eslint-disable-next-line
// eslint-disable-next-line
}, [participantVersion, room]);
const userIds = useMemo(
@ -195,9 +198,13 @@ export default function View() {
}
return (
<div ref={containerRef} style={{
height: "calc(100% - 2rem)"
}} className="w-full overflow-hidden p-3">
<div
ref={containerRef}
style={{
height: "calc(100% - 2rem)",
}}
className="w-full overflow-hidden p-3"
>
<div className="flex h-full w-full flex-col items-center justify-center gap-3">
{rows.map((row) => (
<div key={row.rowIndex} className="flex justify-center gap-3">
@ -217,7 +224,10 @@ export default function View() {
height: layout.tileHeight,
}}
>
<Base type={tile.kind} participant={getParticipantById(tile.participantId)} />
<Base
type={tile.kind}
participant={getParticipantById(tile.participantId)}
/>
</div>
);
})}

View file

@ -7,3 +7,4 @@
- Buttons
- Preview image
- Mobile
- Deaf status is not synced