From a18685fe3123ab16ed44cd8eefd73e703741dde8 Mon Sep 17 00:00:00 2001 From: Isaiah Becker-Mayer Date: Tue, 1 Mar 2022 15:34:17 -0500 Subject: [PATCH] Maintain aspect ratio on Desktop Playback (#635) --- .../teleport/src/Player/DesktopPlayer.tsx | 159 ++++++++---------- .../src/Player/ProgressBar/ProgressBar.tsx | 4 +- .../Player/ProgressBar/ProgressBarDesktop.tsx | 84 +++++++++ .../teleport/src/Player/ProgressBar/index.tsx | 3 +- 4 files changed, 155 insertions(+), 95 deletions(-) create mode 100644 packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx diff --git a/packages/teleport/src/Player/DesktopPlayer.tsx b/packages/teleport/src/Player/DesktopPlayer.tsx index 17946ae0f..3249d3e49 100644 --- a/packages/teleport/src/Player/DesktopPlayer.tsx +++ b/packages/teleport/src/Player/DesktopPlayer.tsx @@ -1,17 +1,16 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import styled from 'styled-components'; -import { throttle } from 'lodash'; -import { dateToUtc } from 'shared/services/loc'; -import { format } from 'date-fns'; + +import { Indicator, Box } from 'design'; import cfg from 'teleport/config'; -import { PlayerClient, PlayerClientEvent } from 'teleport/lib/tdp'; +import { PlayerClient } from 'teleport/lib/tdp'; import { PngFrame, ClientScreenSpec } from 'teleport/lib/tdp/codec'; import { getAccessToken, getHostName } from 'teleport/services/api'; import TdpClientCanvas from 'teleport/components/TdpClientCanvas'; -import ProgressBar from './ProgressBar'; +import { ProgressBarDesktop } from './ProgressBar'; export const DesktopPlayer = ({ sid, @@ -22,96 +21,48 @@ export const DesktopPlayer = ({ clusterId: string; durationMs: number; }) => { - const { playerClient, tdpCliOnPngFrame, tdpCliOnClientScreenSpec } = - useDesktopPlayer({ - sid, - clusterId, - }); - - return ( - - true} - // overflow: 'hidden' is needed to prevent the canvas from outgrowing the container due to some weird css flex idiosyncracy. - // See https://gaurav5430.medium.com/css-flex-positioning-gotchas-child-expands-to-more-than-the-width-allowed-by-the-parent-799c37428dd6. - style={{ display: 'flex', flexGrow: 1, overflow: 'hidden' }} - /> - - - ); -}; - -export const ProgressBarDesktop = (props: { - playerClient: PlayerClient; - durationMs: number; -}) => { - const { playerClient, durationMs } = props; - - const toHuman = (currentMs: number) => { - return format(dateToUtc(new Date(currentMs)), 'mm:ss'); - }; - - const [state, setState] = useState({ - max: durationMs, - min: 0, - current: 0, // the recording always starts at 0 ms - time: toHuman(0), - isPlaying: true, // determines whether play or pause symbol is shown + const { + playerClient, + tdpCliOnPngFrame, + tdpCliOnClientScreenSpec, + showCanvas, + } = useDesktopPlayer({ + sid, + clusterId, }); - useEffect(() => { - playerClient.addListener(PlayerClientEvent.TOGGLE_PLAY_PAUSE, () => { - // setState({...state, isPlaying: !state.isPlaying}) doesn't work because - // the listener is added when state == initialState, and that initialState - // value is effectively hardcoded into its logic. - setState(prevState => { - return { ...prevState, isPlaying: !prevState.isPlaying }; - }); - }); - - const throttledUpdateCurrentTime = throttle( - currentTimeMs => { - setState(prevState => { - return { - ...prevState, - current: currentTimeMs, - time: toHuman(currentTimeMs), - }; - }); - }, - // Magic number to throttle progress bar updates so that the playback is smoother. - 50 - ); - - playerClient.addListener( - PlayerClientEvent.UPDATE_CURRENT_TIME, - currentTimeMs => throttledUpdateCurrentTime(currentTimeMs) - ); - - playerClient.addListener(PlayerClientEvent.SESSION_END, () => { - throttledUpdateCurrentTime.cancel(); - // TODO(isaiah): Make this smoother - // https://github.com/gravitational/webapps/issues/579 - setState(prevState => { - return { ...prevState, current: durationMs }; - }); - }); - - return () => { - throttledUpdateCurrentTime.cancel(); - playerClient.nuke(); - }; - }, [playerClient]); - return ( - playerClient.togglePlayPause()} - move={() => {}} - /> + <> + + {!showCanvas && ( + + + + )} + + true} + // overflow: 'hidden' is needed to prevent the canvas from outgrowing the container due to some weird css flex idiosyncracy. + // See https://gaurav5430.medium.com/css-flex-positioning-gotchas-child-expands-to-more-than-the-width-allowed-by-the-parent-799c37428dd6. + style={{ + alignSelf: 'center', + overflow: 'hidden', + display: showCanvas ? 'flex' : 'none', + }} + /> + + + ); }; @@ -130,6 +81,8 @@ const useDesktopPlayer = ({ .replace(':token', getAccessToken()) ); + const [showCanvas, setShowCanvas] = useState(false); + const tdpCliOnPngFrame = ( ctx: CanvasRenderingContext2D, pngFrame: PngFrame @@ -141,20 +94,40 @@ const useDesktopPlayer = ({ canvas: HTMLCanvasElement, spec: ClientScreenSpec ) => { + const styledPlayer = canvas.parentElement; + const progressBar = styledPlayer.children.namedItem('progressBarDesktop'); + + const fullWidth = styledPlayer.clientWidth; + const fullHeight = styledPlayer.clientHeight - progressBar.clientHeight; + const originalAspectRatio = spec.width / spec.height; + const currentAspectRatio = fullWidth / fullHeight; + + if (originalAspectRatio > currentAspectRatio) { + // Use the full width of the screen and scale the height. + canvas.style.height = `${(fullWidth * spec.height) / spec.width}px`; + } else if (originalAspectRatio < currentAspectRatio) { + // Use the full height of the screen and scale the width. + canvas.style.width = `${(fullHeight * spec.width) / spec.height}px`; + } + canvas.width = spec.width; canvas.height = spec.height; + + setShowCanvas(true); }; return { playerClient, tdpCliOnPngFrame, tdpCliOnClientScreenSpec, + showCanvas, }; }; const StyledPlayer = styled.div` display: flex; flex-direction: column; + justify-content: center; width: 100%; height: 100%; `; diff --git a/packages/teleport/src/Player/ProgressBar/ProgressBar.tsx b/packages/teleport/src/Player/ProgressBar/ProgressBar.tsx index cbe9844ab..a24759074 100644 --- a/packages/teleport/src/Player/ProgressBar/ProgressBar.tsx +++ b/packages/teleport/src/Player/ProgressBar/ProgressBar.tsx @@ -23,7 +23,7 @@ import Slider from './Slider'; export default function ProgressBar(props: ProgressBarProps) { const Icon = props.isPlaying ? Icons.CirclePause : Icons.CirclePlay; return ( - + @@ -51,6 +51,8 @@ export type ProgressBarProps = { current: number; move: (value: any) => void; toggle: () => void; + style?: React.CSSProperties; + id?: string; }; const SliderContainer = styled.div` diff --git a/packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx b/packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx new file mode 100644 index 000000000..4fb2f1661 --- /dev/null +++ b/packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx @@ -0,0 +1,84 @@ +import React, { useState, useEffect } from 'react'; + +import { throttle } from 'lodash'; +import { dateToUtc } from 'shared/services/loc'; +import { format } from 'date-fns'; + +import { PlayerClient, PlayerClientEvent } from 'teleport/lib/tdp'; + +import ProgressBar from './ProgressBar'; + +export const ProgressBarDesktop = (props: { + playerClient: PlayerClient; + durationMs: number; + style?: React.CSSProperties; + id?: string; +}) => { + const { playerClient, durationMs } = props; + + const toHuman = (currentMs: number) => { + return format(dateToUtc(new Date(currentMs)), 'mm:ss'); + }; + + const [state, setState] = useState({ + max: durationMs, + min: 0, + current: 0, // the recording always starts at 0 ms + time: toHuman(0), + isPlaying: true, // determines whether play or pause symbol is shown + }); + + useEffect(() => { + playerClient.addListener(PlayerClientEvent.TOGGLE_PLAY_PAUSE, () => { + // setState({...state, isPlaying: !state.isPlaying}) doesn't work because + // the listener is added when state == initialState, and that initialState + // value is effectively hardcoded into its logic. + setState(prevState => { + return { ...prevState, isPlaying: !prevState.isPlaying }; + }); + }); + + const throttledUpdateCurrentTime = throttle( + currentTimeMs => { + setState(prevState => { + return { + ...prevState, + current: currentTimeMs, + time: toHuman(currentTimeMs), + }; + }); + }, + // Magic number to throttle progress bar updates so that the playback is smoother. + 50 + ); + + playerClient.addListener( + PlayerClientEvent.UPDATE_CURRENT_TIME, + currentTimeMs => throttledUpdateCurrentTime(currentTimeMs) + ); + + playerClient.addListener(PlayerClientEvent.SESSION_END, () => { + throttledUpdateCurrentTime.cancel(); + // TODO(isaiah): Make this smoother + // https://github.com/gravitational/webapps/issues/579 + setState(prevState => { + return { ...prevState, current: durationMs }; + }); + }); + + return () => { + throttledUpdateCurrentTime.cancel(); + playerClient.nuke(); + }; + }, [playerClient]); + + return ( + playerClient.togglePlayPause()} + move={() => {}} + style={props.style} + id={props.id} + /> + ); +}; diff --git a/packages/teleport/src/Player/ProgressBar/index.tsx b/packages/teleport/src/Player/ProgressBar/index.tsx index ef4fc98ac..480df13f9 100644 --- a/packages/teleport/src/Player/ProgressBar/index.tsx +++ b/packages/teleport/src/Player/ProgressBar/index.tsx @@ -16,6 +16,7 @@ limitations under the License. import ProgressBar from './ProgressBar'; import ProgressBarTty from './ProgressBarTty'; +import { ProgressBarDesktop } from './ProgressBarDesktop'; export default ProgressBar; -export { ProgressBarTty }; +export { ProgressBarTty, ProgressBarDesktop };