Files
cinny/src/app/hooks/useCallEmbed.ts
Ajay Bura bc6caddcc8 Add own control buttons for element-call (#2744)
* add mutation observer hok

* add hook to read speaking member by observing iframe content

* display speaking member name in call status bar and improve layout

* fix shrining

* add joined call control bar

* remove chat toggle from room header

* change member speaking icon to mic

* fix joined call control appear in other

* show spinner on end call button

* hide call statusbar for mobile view when room is selected

* make call statusbar more mobile friendly

* fix call status bar item align
2026-03-09 08:34:48 +05:30

143 lines
4.4 KiB
TypeScript

import { createContext, RefObject, useCallback, useContext, useEffect, useState } from 'react';
import { MatrixRTCSession } from 'matrix-js-sdk/lib/matrixrtc/MatrixRTCSession';
import { MatrixClient, Room } from 'matrix-js-sdk';
import { useSetAtom } from 'jotai';
import {
CallEmbed,
ElementCallThemeKind,
ElementWidgetActions,
useClientWidgetApiEvent,
} from '../plugins/call';
import { useMatrixClient } from './useMatrixClient';
import { ThemeKind, useTheme } from './useTheme';
import { callEmbedAtom } from '../state/callEmbed';
import { useResizeObserver } from './useResizeObserver';
import { CallControlState } from '../plugins/call/CallControlState';
import { useCallMembersChange, useCallSession } from './useCall';
import { CallPreferences } from '../state/callPreferences';
const CallEmbedContext = createContext<CallEmbed | undefined>(undefined);
export const CallEmbedContextProvider = CallEmbedContext.Provider;
export const useCallEmbed = (): CallEmbed | undefined => {
const callEmbed = useContext(CallEmbedContext);
return callEmbed;
};
const CallEmbedRefContext = createContext<RefObject<HTMLDivElement> | undefined>(undefined);
export const CallEmbedRefContextProvider = CallEmbedRefContext.Provider;
export const useCallEmbedRef = (): RefObject<HTMLDivElement> => {
const ref = useContext(CallEmbedRefContext);
if (!ref) {
throw new Error('CallEmbedRef is not provided!');
}
return ref;
};
export const createCallEmbed = (
mx: MatrixClient,
room: Room,
dm: boolean,
themeKind: ElementCallThemeKind,
container: HTMLElement,
pref?: CallPreferences
): CallEmbed => {
const rtcSession = mx.matrixRTC.getRoomSession(room);
const ongoing =
MatrixRTCSession.sessionMembershipsForRoom(room, rtcSession.sessionDescription).length > 0;
const intent = CallEmbed.getIntent(dm, ongoing);
const widget = CallEmbed.getWidget(mx, room, intent, themeKind);
const controlState = pref && new CallControlState(pref.microphone, pref.video, pref.sound);
const embed = new CallEmbed(mx, room, widget, container, controlState);
return embed;
};
export const useCallStart = (dm = false) => {
const mx = useMatrixClient();
const theme = useTheme();
const setCallEmbed = useSetAtom(callEmbedAtom);
const callEmbedRef = useCallEmbedRef();
const startCall = useCallback(
(room: Room, pref?: CallPreferences) => {
const container = callEmbedRef.current;
if (!container) {
throw new Error('Failed to start call, No embed container element found!');
}
const callEmbed = createCallEmbed(mx, room, dm, theme.kind, container, pref);
setCallEmbed(callEmbed);
},
[mx, dm, theme, setCallEmbed, callEmbedRef]
);
return startCall;
};
export const useCallJoined = (embed?: CallEmbed): boolean => {
const [joined, setJoined] = useState(embed?.joined ?? false);
useClientWidgetApiEvent(
embed?.call,
ElementWidgetActions.JoinCall,
useCallback(() => {
setJoined(true);
}, [])
);
useEffect(() => {
if (!embed) {
setJoined(false);
}
}, [embed]);
return joined;
};
export const useCallHangupEvent = (embed: CallEmbed, callback: () => void) => {
useClientWidgetApiEvent(embed.call, ElementWidgetActions.HangupCall, callback);
};
export const useCallMemberSoundSync = (embed: CallEmbed) => {
const callSession = useCallSession(embed.room);
useCallMembersChange(
callSession,
useCallback(() => embed.control.applySound(), [embed])
);
};
export const useCallThemeSync = (embed: CallEmbed) => {
const theme = useTheme();
useEffect(() => {
const name: ElementCallThemeKind = theme.kind === ThemeKind.Dark ? 'dark' : 'light';
embed.setTheme(name);
}, [theme.kind, embed]);
};
export const useCallEmbedPlacementSync = (containerViewRef: RefObject<HTMLDivElement>): void => {
const callEmbedRef = useCallEmbedRef();
const syncCallEmbedPlacement = useCallback(() => {
const embedEl = callEmbedRef.current;
const container = containerViewRef.current;
if (!embedEl || !container) return;
embedEl.style.top = `${container.offsetTop}px`;
embedEl.style.left = `${container.offsetLeft}px`;
embedEl.style.width = `${container.clientWidth}px`;
embedEl.style.height = `${container.clientHeight}px`;
}, [callEmbedRef, containerViewRef]);
useResizeObserver(
syncCallEmbedPlacement,
useCallback(() => containerViewRef.current, [containerViewRef])
);
};