forked from github/cinny
* 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
143 lines
4.4 KiB
TypeScript
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])
|
|
);
|
|
};
|