From 08ac6c47196bd0c149f17f58b5952ad633540911 Mon Sep 17 00:00:00 2001 From: Amar Bathwal <110378139+amar-1995@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:14:54 +0530 Subject: [PATCH] feat: add play pause state for desktop, fullscreen fixes --- .../Prebuilt/components/Footer/ChatToggle.tsx | 4 +- .../Prebuilt/components/HMSVideo/HMSVideo.jsx | 1 + .../components/HMSVideo/PlayPauseButton.tsx | 4 +- .../HMSVideo/PlayPauseSeekControls.tsx | 158 ++++++++++++++ .../{SeekControls.tsx => SeekControl.tsx} | 4 +- .../components/HMSVideo/VideoProgress.tsx | 10 +- .../src/Prebuilt/components/HMSVideo/index.ts | 9 +- .../src/Prebuilt/layouts/HLSView.jsx | 193 +++++++++++------- 8 files changed, 292 insertions(+), 91 deletions(-) create mode 100644 packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseSeekControls.tsx rename packages/roomkit-react/src/Prebuilt/components/HMSVideo/{SeekControls.tsx => SeekControl.tsx} (91%) diff --git a/packages/roomkit-react/src/Prebuilt/components/Footer/ChatToggle.tsx b/packages/roomkit-react/src/Prebuilt/components/Footer/ChatToggle.tsx index 5ae0033156..8615b3bac4 100644 --- a/packages/roomkit-react/src/Prebuilt/components/Footer/ChatToggle.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/Footer/ChatToggle.tsx @@ -9,7 +9,7 @@ import { useIsSidepaneTypeOpen, useSidepaneToggle } from '../AppData/useSidepane // @ts-ignore: No implicit Any import { SIDE_PANE_OPTIONS } from '../../common/constants'; -export const ChatToggle = () => { +export const ChatToggle = ({ onClick }: { onClick?: () => void }) => { const countUnreadMessages = useHMSStore(selectUnreadHMSMessagesCount); const isChatOpen = useIsSidepaneTypeOpen(SIDE_PANE_OPTIONS.CHAT); const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT); @@ -21,7 +21,7 @@ export const ChatToggle = () => { }} > - + (onClick ? onClick() : toggleChat())} active={!isChatOpen} data-testid="chat_btn"> diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx index d022f05a23..a673afef4a 100644 --- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/HMSVideo.jsx @@ -9,6 +9,7 @@ export const HMSVideo = forwardRef(({ children, ...props }, videoRef) => { size: '100%', position: 'relative', justifyContent: 'center', + transition: 'all 0.3s ease-in-out', '@md': { height: 'auto', '& video': { diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx index 2c950ab34b..ef29a128ae 100644 --- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseButton.tsx @@ -9,8 +9,8 @@ export const PlayPauseButton = ({ height = 20, }: { isPaused: boolean; - width: number; - height: number; + width?: number; + height?: number; }) => { const { hlsPlayer } = useHMSPlayerContext(); const onClick = async (event: MouseEvent) => { diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseSeekControls.tsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseSeekControls.tsx new file mode 100644 index 0000000000..03267dbaca --- /dev/null +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/PlayPauseSeekControls.tsx @@ -0,0 +1,158 @@ +import React from 'react'; +import { useMedia } from 'react-use'; +import { BackwardArrowIcon, ForwardArrowIcon } from '@100mslive/react-icons'; +import { Box, Flex } from '../../../Layout'; +import { Text } from '../../../Text'; +import { config } from '../../../Theme'; +import { PlayPauseButton } from './PlayPauseButton'; +import { SeekControl } from './SeekControl'; +import { useIsLandscape } from '../../common/hooks'; + +// desktop buttons +export const PlayPauseSeekControls = ({ + isPaused, + onSeekTo, +}: { + isPaused: boolean; + onSeekTo: (value: number) => void; +}) => { + return ( + <> + { + e.stopPropagation(); + onSeekTo(-10); + }} + title="backward" + > + + + + { + e.stopPropagation(); + onSeekTo(10); + }} + title="forward" + > + + + + ); +}; + +// overlay handlers +export const PlayPauseSeekOverlayControls = ({ + isPaused, + showControls, + hoverControlsVisible, +}: { + isPaused: boolean; + showControls: boolean; + hoverControlsVisible: { + seekBackward: boolean; + seekForward: boolean; + pausePlay: boolean; + }; +}) => { + const isMobile = useMedia(config.media.md); + const isLandscape = useIsLandscape(); + + if (!isMobile && !isLandscape) { + // show desktopOverflow icons + return ( + <> + + + + + + 10 secs + + + + + + + + + + + 10 secs + + + + ); + } + + return ( + + + + + + + + + + + + ); +}; diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControls.tsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControl.tsx similarity index 91% rename from packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControls.tsx rename to packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControl.tsx index 830ed0a082..20ba919045 100644 --- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControls.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/SeekControl.tsx @@ -1,7 +1,7 @@ import React, { MouseEventHandler } from 'react'; import { IconButton, Tooltip } from '../../..'; -export const SeekControls = ({ +export const SeekControl = ({ title, onClick, children, @@ -9,7 +9,7 @@ export const SeekControls = ({ }: { title: string; onClick?: MouseEventHandler; - css: any; + css?: any; children: React.ReactNode; }) => { return ( diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/VideoProgress.tsx b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/VideoProgress.tsx index c2e637c10a..42f9eaafc4 100644 --- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/VideoProgress.tsx +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/VideoProgress.tsx @@ -25,8 +25,12 @@ export const VideoProgress = ({ if (videoEl.buffered.length > 0) { bufferProgress = Math.floor(getPercentage(videoEl.buffered?.end(0), duration)); } - setVideoProgress(isNaN(videoProgress) ? 0 : videoProgress); - setBufferProgress(isNaN(bufferProgress) ? 0 : bufferProgress); + if (!isNaN(videoProgress)) { + setVideoProgress(videoProgress); + } + if (!isNaN(bufferProgress)) { + setBufferProgress(bufferProgress); + } }, [videoEl]); const timeupdateHandler = useCallback(() => { if (!videoEl || seekProgress) { @@ -42,7 +46,7 @@ export const VideoProgress = ({ return function cleanup() { videoEl?.removeEventListener('timeupdate', timeupdateHandler); }; - }, [timeupdateHandler, videoEl]); + }, [setProgress, timeupdateHandler, videoEl]); const onProgress = (progress: number[]) => { const progress1 = Math.floor(getPercentage(progress[0], 100)); diff --git a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/index.ts b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/index.ts index e85f278b49..2a488a597c 100644 --- a/packages/roomkit-react/src/Prebuilt/components/HMSVideo/index.ts +++ b/packages/roomkit-react/src/Prebuilt/components/HMSVideo/index.ts @@ -2,15 +2,13 @@ import { LeftControls, RightControls, VideoControls } from './Controls'; // @ts-ignore import { HMSVideo } from './HMSVideo'; -import { PlayPauseButton } from './PlayPauseButton'; -import { SeekControls } from './SeekControls'; +import { PlayPauseSeekControls, PlayPauseSeekOverlayControls } from './PlayPauseSeekControls'; import { VideoProgress } from './VideoProgress'; import { VideoTime } from './VideoTime'; import { VolumeControl } from './VolumeControl'; export const HMSVideoPlayer = { Root: HMSVideo, - PlayPauseButton: PlayPauseButton, Progress: VideoProgress, Duration: VideoTime, Volume: VolumeControl, @@ -19,5 +17,8 @@ export const HMSVideoPlayer = { Left: LeftControls, Right: RightControls, }, - Seeker: SeekControls, + PlayPauseSeekControls: { + Overlay: PlayPauseSeekOverlayControls, + Button: PlayPauseSeekControls, + }, }; diff --git a/packages/roomkit-react/src/Prebuilt/layouts/HLSView.jsx b/packages/roomkit-react/src/Prebuilt/layouts/HLSView.jsx index 2be40db094..d8d2230a75 100644 --- a/packages/roomkit-react/src/Prebuilt/layouts/HLSView.jsx +++ b/packages/roomkit-react/src/Prebuilt/layouts/HLSView.jsx @@ -15,7 +15,7 @@ import { useHMSStore, useHMSVanillaStore, } from '@100mslive/react-sdk'; -import { BackwardArrowIcon, ColoredHandIcon, ForwardArrowIcon, GoLiveIcon } from '@100mslive/react-icons'; +import { ColoredHandIcon, GoLiveIcon } from '@100mslive/react-icons'; import { ChatToggle } from '../components/Footer/ChatToggle'; import { HlsStatsOverlay } from '../components/HlsStatsOverlay'; import { HMSVideoPlayer } from '../components/HMSVideo'; @@ -42,7 +42,7 @@ import { APP_DATA, EMOJI_REACTION_TYPE, POLL_STATE, POLL_VIEWS, SIDE_PANE_OPTION let hlsPlayer; const toastMap = {}; -const ToggleChat = () => { +const ToggleChat = ({ isFullScreen = false }) => { const { elements } = useRoomLayoutConferencingScreen(); const sidepane = useHMSStore(selectAppData(APP_DATA.sidePane)); const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT); @@ -51,7 +51,10 @@ const ToggleChat = () => { const hmsActions = useHMSActions(); useEffect(() => { - match({ sidepane, isMobile, showChat }) + match({ sidepane, isMobile, showChat, isFullScreen }) + .with({ isFullScreen: true }, () => { + hmsActions.setAppData(APP_DATA.sidePane, ''); + }) .with({ isMobile: true, showChat: true, sidepane: P.when(value => !value) }, () => { toggleChat(); }) @@ -61,12 +64,13 @@ const ToggleChat = () => { .otherwise(() => { //do nothing }); - }, [sidepane, isMobile, toggleChat, showChat, hmsActions]); + }, [sidepane, isMobile, toggleChat, showChat, hmsActions, isFullScreen]); return null; }; const HLSView = () => { const videoRef = useRef(null); const hlsViewRef = useRef(); + const { elements } = useRoomLayoutConferencingScreen(); const hlsState = useHMSStore(selectHLSState); const enablHlsStats = useHMSStore(selectAppData(APP_DATA.hlsStats)); const notification = useHMSNotifications(HMSNotificationTypes.POLL_STOPPED); @@ -81,7 +85,11 @@ const HLSView = () => { const [hasCaptions, setHasCaptions] = useState(false); const [currentSelectedQuality, setCurrentSelectedQuality] = useState(null); const [isHlsAutoplayBlocked, setIsHlsAutoplayBlocked] = useState(false); - const [isSeekEnabled, setIsSeekEnabled] = useState(false); + const [hoverControlsVisible, setHoverControlsVisible] = useState({ + seekForward: false, + pausePlay: false, + seekBackward: false, + }); const [isPaused, setIsPaused] = useState(false); const [show, toggle] = useToggle(false); const lastHlsUrl = usePrevious(hlsUrl); @@ -93,6 +101,8 @@ const HLSView = () => { const controlsTimerRef = useRef(); const [seekProgress, setSeekProgress] = useState(false); const isFullScreenSupported = screenfull.isEnabled; + const toggleChat = useSidepaneToggle(SIDE_PANE_OPTIONS.CHAT); + const showChat = !!elements?.chat; const isMobile = useMedia(config.media.md); const isLandscape = useIsLandscape(); @@ -233,7 +243,19 @@ const HLSView = () => { setIsVideoLive(isLive); }; - const playbackEventHandler = data => setIsPaused(data.state === HLSPlaybackState.paused); + const playbackEventHandler = data => { + setIsPaused(data.state === HLSPlaybackState.paused); + setHoverControlsVisible({ + ...hoverControlsVisible, + pausePlay: true, + }); + setTimeout(() => { + setHoverControlsVisible({ + ...hoverControlsVisible, + pausePlay: false, + }); + }, 2000); + }; const captionEnabledEventHandler = isCaptionEnabled => { setIsCaptionEnabled(isCaptionEnabled); }; @@ -314,28 +336,63 @@ const HLSView = () => { }; }, [controlsVisible, isFullScreen, seekProgress, qualityDropDownOpen]); - const onSeekTo = useCallback(seek => { - hlsPlayer?.seekTo(videoRef.current?.currentTime + seek); - }, []); + const onSeekTo = useCallback( + seek => { + match({ isLandscape, isMobile, seek }) + .with({ seek: -10, isMobile: false, isLandscape: false }, () => { + setHoverControlsVisible({ ...hoverControlsVisible, seekBackward: true }); + setTimeout(() => { + setHoverControlsVisible({ + ...hoverControlsVisible, + seekBackward: false, + }); + }, 1000); + }) + .with({ seek: 10, isMobile: false, isLandscape: false }, () => { + setHoverControlsVisible({ ...hoverControlsVisible, seekForward: true }); + setTimeout(() => { + setHoverControlsVisible({ + ...hoverControlsVisible, + seekForward: false, + }); + }, 1000); + }) + .otherwise(() => null); + hlsPlayer?.seekTo(videoRef.current?.currentTime + seek); + }, + [hoverControlsVisible, isLandscape, isMobile], + ); const onDoubleClickHandler = useCallback( event => { if (!(isMobile || isLandscape) || hlsState?.variants[0]?.playlist_type !== HLSPlaylistType.DVR) { return; } const sidePercentage = (event.screenX * 100) / event.target.clientWidth; - setIsSeekEnabled(true); // there is space for pause/unpause button if (sidePercentage < 45) { + setHoverControlsVisible({ + ...hoverControlsVisible, + seekBackward: true, + }); onSeekTo(-10); } else { + setHoverControlsVisible({ + ...hoverControlsVisible, + seekForward: true, + }); onSeekTo(10); } setTimeout(() => { - setIsSeekEnabled(false); - }, 200); + setHoverControlsVisible({ + ...hoverControlsVisible, + seekForward: false, + seekBackward: false, + }); + }, 1000); }, - [hlsState?.variants, isLandscape, isMobile, onSeekTo], + [hlsState?.variants, hoverControlsVisible, isLandscape, isMobile, onSeekTo], ); + const onClickHandler = useCallback(async () => { match({ isMobile, isLandscape, playlist_type: hlsState?.variants[0]?.playlist_type }) .with({ playlist_type: HLSPlaylistType.DVR, isMobile: false, isLandscape: false }, async () => { @@ -356,6 +413,7 @@ const HLSView = () => { ) .otherwise(() => null); }, [hlsState?.variants, isLandscape, isMobile, isPaused]); + const onHoverHandler = useCallback( event => { event.preventDefault(); @@ -423,6 +481,7 @@ const HLSView = () => { justify="center" css={{ flex: isLandscape ? '2 1 0' : '1 1 0', + transition: 'all 0.3s ease-in-out', '&:fullscreen': { '& video': { height: 'unset !important', @@ -484,53 +543,37 @@ const HLSView = () => { }} > <> + {!(isMobile || isLandscape) && ( + + {!showLoader && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR && ( + + )} + + )} {isMobile || isLandscape ? ( <> - - {!showLoader && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR && ( - <> - - - - - - - - - - - )} - + {!showLoader && hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR && ( + + )} { }} > - {isLandscape && } + {(isLandscape || (isMobile && isFullScreen)) && showChat && ( + { + if (isFullScreen) { + toggle(); + } + toggleChat(); + }} + /> + )} {hasCaptions && !isHlsAutoplayBlocked && } {hlsViewRef.current && availableLayers.length > 0 && !isHlsAutoplayBlocked ? ( { <> {hlsState?.variants[0]?.playlist_type === HLSPlaylistType.DVR ? ( <> - { - onSeekTo(-10); - }} - title="backward" - > - - - - { - onSeekTo(10); - }} - title="forward" - > - - + {!isVideoLive ? : null} ) : null} @@ -631,7 +667,8 @@ const HLSView = () => { )} { + onClick={async e => { + e.stopPropagation(); await hlsPlayer?.seekToLivePosition(); setIsVideoLive(true); }} @@ -690,7 +727,7 @@ const HLSView = () => { - + {isMobile && !isFullScreen && } );