From c7dbfca53dcaa14bac7a401697d6cebd95d87555 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 7 Aug 2022 19:04:00 +0200 Subject: [PATCH 1/9] Add icons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/icons/Fullscreen.svg | 3 +++ src/icons/FullscreenExit.svg | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 src/icons/Fullscreen.svg create mode 100644 src/icons/FullscreenExit.svg 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 @@ + + + From 7ca08f2f301996c4b203091667f09b80a838d200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 7 Aug 2022 19:04:59 +0200 Subject: [PATCH 2/9] Add `FullscreenButton` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/button/Button.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/button/Button.tsx b/src/button/Button.tsx index 2c2605f..bc6359d 100644 --- a/src/button/Button.tsx +++ b/src/button/Button.tsx @@ -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,20 @@ export function AudioButton({ volume, ...rest }: AudioButtonProps) { ); } + +interface FullscreenButtonProps extends Omit { + fullscreen?: boolean; +} + +export function FullscreenButton({ + fullscreen, + ...rest +}: FullscreenButtonProps) { + return ( + "Full screen"}> + + + ); +} From 9af122b96ea09f891203b883007ef5fec59895ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 7 Aug 2022 19:05:49 +0200 Subject: [PATCH 3/9] Add `useFullscreen()` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/video-grid/useFullscreen.tsx | 56 ++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/video-grid/useFullscreen.tsx diff --git a/src/video-grid/useFullscreen.tsx b/src/video-grid/useFullscreen.tsx new file mode 100644 index 0000000..2d354a4 --- /dev/null +++ b/src/video-grid/useFullscreen.tsx @@ -0,0 +1,56 @@ +/* +Copyright 2022 New Vector Ltd + + +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 { useCallback, useState } from "react"; + +import { Participant } from "../room/InCallView"; +import { useEventTarget } from "../useEvents"; + +export function useFullscreen(ref: React.RefObject): { + toggleFullscreen: (participant: Participant) => void; + fullscreenParticipant: Participant | null; +} { + const [fullscreenParticipant, setFullscreenParticipant] = + useState(null); + + const toggleFullscreen = useCallback( + (participant: Participant) => { + if (fullscreenParticipant) { + document.exitFullscreen(); + setFullscreenParticipant(null); + } else { + try { + ref.current.requestFullscreen(); + setFullscreenParticipant(participant); + } catch (error) { + console.warn("Failed to fullscreen:", error); + } + } + }, + [fullscreenParticipant, setFullscreenParticipant, ref] + ); + + const onFullscreenChanged = useCallback(() => { + if (!document.fullscreenElement) { + setFullscreenParticipant(null); + } + }, [setFullscreenParticipant]); + + useEventTarget(ref.current, "fullscreenchange", onFullscreenChanged); + + return { toggleFullscreen, fullscreenParticipant }; +} From 305c2cb806f10aec55cf117f01538613488d7094 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 7 Aug 2022 19:09:45 +0200 Subject: [PATCH 4/9] Add support for screen-sharing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/room/InCallView.module.css | 6 ++ src/room/InCallView.tsx | 136 +++++++++++++++++--------- src/video-grid/VideoTile.jsx | 26 +++-- src/video-grid/VideoTile.module.css | 8 +- src/video-grid/VideoTileContainer.jsx | 5 +- 5 files changed, 127 insertions(+), 54 deletions(-) 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..b6329a7 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,7 @@ interface Props { roomId: string; unencryptedEventsFromUsers: Set; } -interface Participant { +export interface Participant { id: string; callFeed: CallFeed; focused: boolean; @@ -100,7 +102,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 +165,107 @@ export function InCallView({ ); }, []); + const renderContent = useCallback(() => { + 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 ( -
+