mirror of
https://github.com/cinnyapp/cinny.git
synced 2025-11-06 23:30:28 +03:00
add in call state (mute/video/hangup) context and transport handling for said contexts
This commit is contained in:
parent
14206dbb96
commit
f76641b538
1 changed files with 164 additions and 16 deletions
|
|
@ -1,6 +1,28 @@
|
||||||
import React, { createContext, useState, useContext, useMemo, useCallback, ReactNode } from 'react';
|
import React, {
|
||||||
|
createContext,
|
||||||
|
useState,
|
||||||
|
useContext,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
ReactNode,
|
||||||
|
useEffect,
|
||||||
|
} from 'react';
|
||||||
import { logger } from 'matrix-js-sdk/lib/logger';
|
import { logger } from 'matrix-js-sdk/lib/logger';
|
||||||
import { WidgetApiToWidgetAction, ITransport, WidgetApiAction } from 'matrix-widget-api';
|
import {
|
||||||
|
WidgetApiToWidgetAction,
|
||||||
|
ITransport,
|
||||||
|
WidgetApiAction,
|
||||||
|
WidgetApiFromWidgetAction,
|
||||||
|
} from 'matrix-widget-api';
|
||||||
|
|
||||||
|
interface MediaStatePayload {
|
||||||
|
audioEnabled?: boolean;
|
||||||
|
videoEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const WIDGET_MEDIA_STATE_UPDATE_ACTION = 'io.element.device_mute';
|
||||||
|
|
||||||
|
const SET_MEDIA_STATE_ACTION = 'io.element.device_mute';
|
||||||
|
|
||||||
interface CallContextState {
|
interface CallContextState {
|
||||||
activeCallRoomId: string | null;
|
activeCallRoomId: string | null;
|
||||||
|
|
@ -12,6 +34,10 @@ interface CallContextState {
|
||||||
action: WidgetApiToWidgetAction | string,
|
action: WidgetApiToWidgetAction | string,
|
||||||
data: T
|
data: T
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
|
isAudioEnabled: boolean;
|
||||||
|
isVideoEnabled: boolean;
|
||||||
|
toggleAudio: () => Promise<void>;
|
||||||
|
toggleVideo: () => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CallContext = createContext<CallContextState | undefined>(undefined);
|
const CallContext = createContext<CallContextState | undefined>(undefined);
|
||||||
|
|
@ -20,53 +46,133 @@ interface CallProviderProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DEFAULT_AUDIO_ENABLED = false;
|
||||||
|
const DEFAULT_VIDEO_ENABLED = false;
|
||||||
|
|
||||||
export function CallProvider({ children }: CallProviderProps) {
|
export function CallProvider({ children }: CallProviderProps) {
|
||||||
const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null);
|
const [activeCallRoomId, setActiveCallRoomIdState] = useState<string | null>(null);
|
||||||
const [activeApiTransport, setActiveApiTransport] = useState<ITransport | null>(null);
|
const [activeApiTransport, setActiveApiTransportState] = useState<ITransport | null>(null);
|
||||||
const [transportRoomId, setTransportRoomId] = useState<string | null>(null);
|
const [transportRoomId, setTransportRoomId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const [isAudioEnabled, setIsAudioEnabledState] = useState<boolean>(DEFAULT_AUDIO_ENABLED);
|
||||||
|
const [isVideoEnabled, setIsVideoEnabledState] = useState<boolean>(DEFAULT_VIDEO_ENABLED);
|
||||||
|
|
||||||
|
const resetMediaState = useCallback(() => {
|
||||||
|
logger.debug('CallContext: Resetting media state to defaults.');
|
||||||
|
setIsAudioEnabledState(DEFAULT_AUDIO_ENABLED);
|
||||||
|
setIsVideoEnabledState(DEFAULT_VIDEO_ENABLED);
|
||||||
|
}, []);
|
||||||
|
|
||||||
const setActiveCallRoomId = useCallback(
|
const setActiveCallRoomId = useCallback(
|
||||||
(roomId: string | null) => {
|
(roomId: string | null) => {
|
||||||
logger.debug(`CallContext: Setting activeCallRoomId to ${roomId}`);
|
logger.warn(`CallContext: Setting activeCallRoomId to ${roomId}`);
|
||||||
|
const previousRoomId = activeCallRoomId;
|
||||||
setActiveCallRoomIdState(roomId);
|
setActiveCallRoomIdState(roomId);
|
||||||
|
|
||||||
|
if (roomId !== previousRoomId) {
|
||||||
|
logger.debug(`CallContext: Active call room changed, resetting media state.`);
|
||||||
|
resetMediaState();
|
||||||
|
}
|
||||||
|
|
||||||
if (roomId === null || roomId !== transportRoomId) {
|
if (roomId === null || roomId !== transportRoomId) {
|
||||||
logger.debug(
|
logger.warn(
|
||||||
`CallContext: Clearing active transport because active room changed or was cleared.`
|
`CallContext: Clearing active transport because active room changed to ${roomId} or was cleared.`
|
||||||
);
|
);
|
||||||
setActiveApiTransport(null);
|
setActiveApiTransportState(null);
|
||||||
setTransportRoomId(null);
|
setTransportRoomId(null);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[transportRoomId]
|
[transportRoomId, resetMediaState, activeCallRoomId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const hangUp = useCallback(() => {
|
const hangUp = useCallback(() => {
|
||||||
logger.debug(`CallContext: Hang up called.`);
|
logger.debug(`CallContext: Hang up called.`);
|
||||||
setActiveCallRoomIdState(null);
|
setActiveCallRoomIdState(null);
|
||||||
logger.debug(`CallContext: Clearing active transport due to hangup.`);
|
logger.debug(`CallContext: Clearing active transport due to hangup.`);
|
||||||
setActiveApiTransport(null);
|
setActiveApiTransportState(null);
|
||||||
setTransportRoomId(null);
|
setTransportRoomId(null);
|
||||||
|
resetMediaState();
|
||||||
|
}, [resetMediaState]);
|
||||||
|
|
||||||
|
const setActiveTransport = useCallback((transport: ITransport | null, roomId: string | null) => {
|
||||||
|
setActiveApiTransportState(transport);
|
||||||
|
setTransportRoomId(roomId);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const registerActiveTransport = useCallback(
|
const registerActiveTransport = useCallback(
|
||||||
(roomId: string | null, transport: ITransport | null) => {
|
(roomId: string | null, transport: ITransport | null) => {
|
||||||
|
if (activeApiTransport && activeApiTransport !== transport) {
|
||||||
|
logger.debug(`CallContext: Cleaning up listeners for previous transport instance.`);
|
||||||
|
}
|
||||||
|
|
||||||
if (roomId && transport) {
|
if (roomId && transport) {
|
||||||
logger.debug(`CallContext: Registering active transport for room ${roomId}.`);
|
logger.debug(`CallContext: Registering active transport for room ${roomId}.`);
|
||||||
setActiveApiTransport(transport);
|
setActiveTransport(transport, roomId);
|
||||||
setTransportRoomId(roomId);
|
|
||||||
} else if (roomId === transportRoomId || roomId === null) {
|
} else if (roomId === transportRoomId || roomId === null) {
|
||||||
logger.debug(`CallContext: Clearing active transport for room ${transportRoomId}.`);
|
logger.debug(`CallContext: Clearing active transport for room ${transportRoomId}.`);
|
||||||
setActiveApiTransport(null);
|
setActiveTransport(null, null);
|
||||||
setTransportRoomId(null);
|
resetMediaState();
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`CallContext: Ignoring transport clear request for room ${roomId}, as current transport belongs to ${transportRoomId}.`
|
`CallContext: Ignoring transport registration/clear request for room ${roomId}, as current transport belongs to ${transportRoomId}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[transportRoomId]
|
[activeApiTransport, transportRoomId, setActiveTransport, resetMediaState]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!activeApiTransport || !activeCallRoomId || transportRoomId !== activeCallRoomId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const transport = activeApiTransport;
|
||||||
|
|
||||||
|
const handleHangup = (ev: CustomEvent) => {
|
||||||
|
logger.warn(
|
||||||
|
`CallContext: Received hangup action from widget in room ${activeCallRoomId}.`,
|
||||||
|
ev
|
||||||
|
);
|
||||||
|
hangUp();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMediaStateUpdate = (ev: CustomEvent<MediaStatePayload>) => {
|
||||||
|
ev.preventDefault();
|
||||||
|
logger.debug(
|
||||||
|
`CallContext: Received media state update from widget in room ${activeCallRoomId}:`,
|
||||||
|
ev.detail
|
||||||
|
);
|
||||||
|
const { audioEnabled, videoEnabled } = ev.detail;
|
||||||
|
if (typeof audioEnabled === 'boolean' && audioEnabled !== isAudioEnabled) {
|
||||||
|
logger.debug(`CallContext: Updating audio enabled state from widget: ${audioEnabled}`);
|
||||||
|
setIsAudioEnabledState(audioEnabled);
|
||||||
|
}
|
||||||
|
if (typeof videoEnabled === 'boolean' && videoEnabled !== isVideoEnabled) {
|
||||||
|
logger.debug(`CallContext: Updating video enabled state from widget: ${videoEnabled}`);
|
||||||
|
setIsVideoEnabledState(videoEnabled);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.debug(`CallContext: Setting up listeners for transport in room ${activeCallRoomId}`);
|
||||||
|
transport.on(`action:${WidgetApiFromWidgetAction.HangupCall}`, handleHangup); // Use standard hangup action name
|
||||||
|
transport.on(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
logger.debug(`CallContext: Cleaning up listeners for transport in room ${activeCallRoomId}`);
|
||||||
|
if (transport) {
|
||||||
|
transport.off(`action:${WidgetApiFromWidgetAction.HangupCall}`, handleHangup);
|
||||||
|
transport.off(`action:${WIDGET_MEDIA_STATE_UPDATE_ACTION}`, handleMediaStateUpdate);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
activeApiTransport,
|
||||||
|
activeCallRoomId,
|
||||||
|
transportRoomId,
|
||||||
|
hangUp,
|
||||||
|
isAudioEnabled,
|
||||||
|
isVideoEnabled,
|
||||||
|
]);
|
||||||
|
|
||||||
const sendWidgetAction = useCallback(
|
const sendWidgetAction = useCallback(
|
||||||
async <T = unknown,>(action: WidgetApiToWidgetAction | string, data: T): Promise<void> => {
|
async <T = unknown,>(action: WidgetApiToWidgetAction | string, data: T): Promise<void> => {
|
||||||
if (!activeApiTransport) {
|
if (!activeApiTransport) {
|
||||||
|
|
@ -89,12 +195,46 @@ export function CallProvider({ children }: CallProviderProps) {
|
||||||
await activeApiTransport.send<T>(action as WidgetApiAction, data);
|
await activeApiTransport.send<T>(action as WidgetApiAction, data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`CallContext: Error sending action '${action}':`, error);
|
logger.error(`CallContext: Error sending action '${action}':`, error);
|
||||||
return Promise.reject(error);
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeApiTransport, activeCallRoomId, transportRoomId]
|
[activeApiTransport, activeCallRoomId, transportRoomId]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const toggleAudio = useCallback(async () => {
|
||||||
|
const newState = !isAudioEnabled;
|
||||||
|
logger.debug(`CallContext: Toggling audio. New state: enabled=${newState}`);
|
||||||
|
setIsAudioEnabledState(newState);
|
||||||
|
try {
|
||||||
|
await sendWidgetAction(SET_MEDIA_STATE_ACTION, {
|
||||||
|
audioEnabled: newState,
|
||||||
|
videoEnabled: isVideoEnabled,
|
||||||
|
});
|
||||||
|
logger.debug(`CallContext: Successfully sent audio toggle action.`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`CallContext: Failed to send audio toggle action. Reverting state.`, error);
|
||||||
|
setIsAudioEnabledState(!newState);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}, [isAudioEnabled, isVideoEnabled, sendWidgetAction]);
|
||||||
|
|
||||||
|
const toggleVideo = useCallback(async () => {
|
||||||
|
const newState = !isVideoEnabled;
|
||||||
|
logger.debug(`CallContext: Toggling video. New state: enabled=${newState}`);
|
||||||
|
setIsVideoEnabledState(newState);
|
||||||
|
try {
|
||||||
|
await sendWidgetAction(SET_MEDIA_STATE_ACTION, {
|
||||||
|
audioEnabled: isAudioEnabled,
|
||||||
|
videoEnabled: newState,
|
||||||
|
});
|
||||||
|
logger.debug(`CallContext: Successfully sent video toggle action.`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`CallContext: Failed to send video toggle action. Reverting state.`, error);
|
||||||
|
setIsVideoEnabledState(!newState);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}, [isVideoEnabled, isAudioEnabled, sendWidgetAction]);
|
||||||
|
|
||||||
const contextValue = useMemo<CallContextState>(
|
const contextValue = useMemo<CallContextState>(
|
||||||
() => ({
|
() => ({
|
||||||
activeCallRoomId,
|
activeCallRoomId,
|
||||||
|
|
@ -103,6 +243,10 @@ export function CallProvider({ children }: CallProviderProps) {
|
||||||
activeApiTransport,
|
activeApiTransport,
|
||||||
registerActiveTransport,
|
registerActiveTransport,
|
||||||
sendWidgetAction,
|
sendWidgetAction,
|
||||||
|
isAudioEnabled,
|
||||||
|
isVideoEnabled,
|
||||||
|
toggleAudio,
|
||||||
|
toggleVideo,
|
||||||
}),
|
}),
|
||||||
[
|
[
|
||||||
activeCallRoomId,
|
activeCallRoomId,
|
||||||
|
|
@ -111,6 +255,10 @@ export function CallProvider({ children }: CallProviderProps) {
|
||||||
activeApiTransport,
|
activeApiTransport,
|
||||||
registerActiveTransport,
|
registerActiveTransport,
|
||||||
sendWidgetAction,
|
sendWidgetAction,
|
||||||
|
isAudioEnabled,
|
||||||
|
isVideoEnabled,
|
||||||
|
toggleAudio,
|
||||||
|
toggleVideo,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue