diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 2c2605f..c29b5f2 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -13,7 +13,7 @@ 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, { forwardRef } from "react"; +import React, { forwardRef, useCallback } from "react"; import { PressEvent } from "@react-types/shared"; import classNames from "classnames"; import { useButton } from "@react-aria/button"; @@ -29,6 +29,8 @@ import { ReactComponent as ScreenshareIcon } from "../icons/Screenshare.svg"; import { ReactComponent as SettingsIcon } from "../icons/Settings.svg"; import { ReactComponent as AddUserIcon } from "../icons/AddUser.svg"; import { ReactComponent as ArrowDownIcon } from "../icons/ArrowDown.svg"; +import { ReactComponent as Fullscreen } from "../icons/Fullscreen.svg"; +import { ReactComponent as FullscreenExit } from "../icons/FullscreenExit.svg"; import { TooltipTrigger } from "../Tooltip"; import { VolumeIcon } from "./VolumeIcon"; @@ -262,3 +264,24 @@ export function AudioButton({ volume, ...rest }: AudioButtonProps) { ); } + +interface FullscreenButtonProps extends Omit { + fullscreen?: boolean; +} + +export function FullscreenButton({ + fullscreen, + ...rest +}: FullscreenButtonProps) { + const getTooltip = useCallback(() => { + return fullscreen ? "Exit full screen" : "Full screen"; + }, [fullscreen]); + + return ( + + + + ); +} diff --git a/src/icons/Fullscreen.svg b/src/icons/Fullscreen.svg new file mode 100644 index 0000000..ad8d2b7 --- /dev/null +++ b/src/icons/Fullscreen.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/icons/FullscreenExit.svg b/src/icons/FullscreenExit.svg new file mode 100644 index 0000000..f7b8044 --- /dev/null +++ b/src/icons/FullscreenExit.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/room/InCallView.module.css b/src/room/InCallView.module.css index 054e27d..f8783bf 100644 --- a/src/room/InCallView.module.css +++ b/src/room/InCallView.module.css @@ -54,6 +54,12 @@ limitations under the License. margin-right: 0px; } +.footerFullscreen { + position: absolute; + width: 100%; + bottom: 0; +} + .avatar { position: absolute; top: 50%; diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 79f1b64..680f9f2 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -14,10 +14,11 @@ See the License for the specific language governing permissions and limitations under the License. */ -import React, { useCallback, useMemo } from "react"; +import React, { useCallback, useMemo, useRef } from "react"; import { usePreventScroll } from "@react-aria/overlays"; import { GroupCall, MatrixClient } from "matrix-js-sdk"; import { CallFeed } from "matrix-js-sdk/src/webrtc/callFeed"; +import classNames from "classnames"; import styles from "./InCallView.module.css"; import { @@ -46,6 +47,7 @@ import { useMediaHandler } from "../settings/useMediaHandler"; import { useShowInspector } from "../settings/useSetting"; import { useModalTriggerState } from "../Modal"; import { useAudioContext } from "../video-grid/useMediaStream"; +import { useFullscreen } from "../video-grid/useFullscreen"; const canScreenshare = "getDisplayMedia" in (navigator.mediaDevices ?? {}); // There is currently a bug in Safari our our code with cloning and sending MediaStreams @@ -72,7 +74,8 @@ interface Props { roomId: string; unencryptedEventsFromUsers: Set; } -interface Participant { + +export interface Participant { id: string; callFeed: CallFeed; focused: boolean; @@ -100,7 +103,9 @@ export function InCallView({ unencryptedEventsFromUsers, }: Props) { usePreventScroll(); + const elementRef = useRef(); const [layout, setLayout] = useVideoGridLayout(screenshareFeeds.length > 0); + const { toggleFullscreen, fullscreenParticipant } = useFullscreen(elementRef); const [audioContext, audioDestination, audioRef] = useAudioContext(); const { audioOutput } = useMediaHandler(); @@ -161,65 +166,107 @@ export function InCallView({ ); }, []); + const renderContent = useCallback((): JSX.Element => { + if (items.length === 0) { + return ( +
+

Waiting for other participants...

+
+ ); + } + if (fullscreenParticipant) { + return ( + + ); + } + + return ( + + {({ item, ...rest }: { item: Participant; [x: string]: unknown }) => ( + 2 || item.focused} + audioOutputDevice={audioOutput} + audioContext={audioContext} + audioDestination={audioDestination} + disableSpeakingIndicator={items.length < 3} + isFullscreen={fullscreenParticipant} + onFullscreen={toggleFullscreen} + {...rest} + /> + )} + + ); + }, [ + fullscreenParticipant, + items, + audioContext, + audioDestination, + audioOutput, + layout, + renderAvatar, + toggleFullscreen, + ]); + const { modalState: rageshakeRequestModalState, modalProps: rageshakeRequestModalProps, } = useRageshakeRequestModal(groupCall.room.roomId); + const footerClassNames = classNames(styles.footer, { + [styles.footerFullscreen]: fullscreenParticipant, + }); + return ( -
+