Initial support for Hand Raise feature

Signed-off-by: Milton Moura <miltonmoura@gmail.com>
This commit is contained in:
Milton Moura
2024-08-07 01:58:14 +00:00
parent cec7fc8f5b
commit 48cf487e0a
8 changed files with 192 additions and 8 deletions

View File

@@ -10,7 +10,14 @@ import {
RoomContext,
useLocalParticipant,
} from "@livekit/components-react";
import { ConnectionState, Room } from "livekit-client";
import {
ConnectionState,
// eslint-disable-next-line camelcase
DataPacket_Kind,
Participant,
Room,
RoomEvent,
} from "livekit-client";
import { MatrixClient } from "matrix-js-sdk/src/client";
import {
FC,
@@ -39,6 +46,7 @@ import {
MicButton,
VideoButton,
ShareScreenButton,
RaiseHandButton,
SettingsButton,
} from "../button";
import { Header, LeftNav, RightNav, RoomHeaderInfo } from "../Header";
@@ -78,6 +86,7 @@ import { makeOneOnOneLayout } from "../grid/OneOnOneLayout";
import { makeSpotlightExpandedLayout } from "../grid/SpotlightExpandedLayout";
import { makeSpotlightLandscapeLayout } from "../grid/SpotlightLandscapeLayout";
import { makeSpotlightPortraitLayout } from "../grid/SpotlightPortraitLayout";
import { RaisedHandsProvider, useRaisedHands } from "./useRaisedHands";
const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {});
@@ -130,12 +139,14 @@ export const ActiveCall: FC<ActiveCallProps> = (props) => {
return (
<RoomContext.Provider value={livekitRoom}>
<InCallView
{...props}
vm={vm}
livekitRoom={livekitRoom}
connState={connState}
/>
<RaisedHandsProvider>
<InCallView
{...props}
vm={vm}
livekitRoom={livekitRoom}
connState={connState}
/>
</RaisedHandsProvider>
</RoomContext.Provider>
);
};
@@ -298,6 +309,34 @@ export const InCallView: FC<InCallViewProps> = ({
[vm],
);
const { raisedHands, setRaisedHands } = useRaisedHands();
const isHandRaised = raisedHands.includes(
localParticipant.identity.split(":")[0] +
":" +
localParticipant.identity.split(":")[1],
);
useEffect(() => {
const handleDataReceived = (
payload: Uint8Array,
participant?: Participant,
// eslint-disable-next-line camelcase
kind?: DataPacket_Kind,
): void => {
const decoder = new TextDecoder();
const strData = decoder.decode(payload);
// get json object from strData
const data = JSON.parse(strData);
setRaisedHands(data.raisedHands);
};
livekitRoom.on(RoomEvent.DataReceived, handleDataReceived);
return (): void => {
livekitRoom.off(RoomEvent.DataReceived, handleDataReceived);
};
}, [livekitRoom, setRaisedHands]);
useEffect(() => {
widget?.api.transport
.send(
@@ -479,6 +518,37 @@ export const InCallView: FC<InCallViewProps> = ({
.catch(logger.error);
}, [localParticipant, isScreenShareEnabled]);
const toggleRaisedHand = useCallback(() => {
// TODO: wtf
const userId =
localParticipant.identity.split(":")[0] +
":" +
localParticipant.identity.split(":")[1];
const raisedHand = raisedHands.includes(userId);
let result = raisedHands;
if (raisedHand) {
result = raisedHands.filter((id) => id !== userId);
} else {
result = [...raisedHands, userId];
}
try {
const strData = JSON.stringify({
raisedHands: result,
});
const encoder = new TextEncoder();
const data = encoder.encode(strData);
livekitRoom.localParticipant.publishData(data, { reliable: true });
setRaisedHands(result);
} catch (e) {
logger.error(e);
}
}, [
livekitRoom.localParticipant,
localParticipant.identity,
raisedHands,
setRaisedHands,
]);
let footer: JSX.Element | null;
if (noControls) {
@@ -513,7 +583,14 @@ export const InCallView: FC<InCallViewProps> = ({
/>,
);
}
buttons.push(<SettingsButton key="4" onClick={openSettings} />);
buttons.push(
<RaiseHandButton
key="4"
onClick={toggleRaisedHand}
raised={isHandRaised}
/>,
);
buttons.push(<SettingsButton key="5" onClick={openSettings} />);
}
buttons.push(

View File

@@ -0,0 +1,47 @@
/*
Copyright 2024 Milton Moura <miltonmoura@gmail.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React, { createContext, useContext, useState, ReactNode } from "react";
interface RaisedHandsContextType {
raisedHands: string[];
setRaisedHands: React.Dispatch<React.SetStateAction<string[]>>;
}
const RaisedHandsContext = createContext<RaisedHandsContextType | undefined>(
undefined,
);
export const useRaisedHands = (): RaisedHandsContextType => {
const context = useContext(RaisedHandsContext);
if (!context) {
throw new Error("useRaisedHands must be used within a RaisedHandsProvider");
}
return context;
};
export const RaisedHandsProvider = ({
children,
}: {
children: ReactNode;
}): JSX.Element => {
const [raisedHands, setRaisedHands] = useState<string[]>([]);
return (
<RaisedHandsContext.Provider value={{ raisedHands, setRaisedHands }}>
{children}
</RaisedHandsContext.Provider>
);
};