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(undefined); export const CallEmbedContextProvider = CallEmbedContext.Provider; export const useCallEmbed = (): CallEmbed | undefined => { const callEmbed = useContext(CallEmbedContext); return callEmbed; }; const CallEmbedRefContext = createContext | undefined>(undefined); export const CallEmbedRefContextProvider = CallEmbedRefContext.Provider; export const useCallEmbedRef = (): RefObject => { 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): 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]) ); };