Skip to content
This repository has been archived by the owner on Feb 8, 2024. It is now read-only.

Commit

Permalink
Maintain aspect ratio on Desktop Playback (#635)
Browse files Browse the repository at this point in the history
  • Loading branch information
Isaiah Becker-Mayer authored Mar 1, 2022
1 parent 0e0766e commit a18685f
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 95 deletions.
159 changes: 66 additions & 93 deletions packages/teleport/src/Player/DesktopPlayer.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -22,96 +21,48 @@ export const DesktopPlayer = ({
clusterId: string;
durationMs: number;
}) => {
const { playerClient, tdpCliOnPngFrame, tdpCliOnClientScreenSpec } =
useDesktopPlayer({
sid,
clusterId,
});

return (
<StyledPlayer>
<TdpClientCanvas
tdpCli={playerClient}
tdpCliOnPngFrame={tdpCliOnPngFrame}
tdpCliOnClientScreenSpec={tdpCliOnClientScreenSpec}
onContextMenu={() => 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' }}
/>
<ProgressBarDesktop playerClient={playerClient} durationMs={durationMs} />
</StyledPlayer>
);
};

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 (
<ProgressBar
{...state}
toggle={() => playerClient.togglePlayPause()}
move={() => {}}
/>
<>
<StyledPlayer>
{!showCanvas && (
<Box textAlign="center" m={10}>
<Indicator />
</Box>
)}

<TdpClientCanvas
tdpCli={playerClient}
tdpCliOnPngFrame={tdpCliOnPngFrame}
tdpCliOnClientScreenSpec={tdpCliOnClientScreenSpec}
onContextMenu={() => 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',
}}
/>
<ProgressBarDesktop
playerClient={playerClient}
durationMs={durationMs}
style={{
display: showCanvas ? 'flex' : 'none',
}}
id="progressBarDesktop"
/>
</StyledPlayer>
</>
);
};

Expand All @@ -130,6 +81,8 @@ const useDesktopPlayer = ({
.replace(':token', getAccessToken())
);

const [showCanvas, setShowCanvas] = useState(false);

const tdpCliOnPngFrame = (
ctx: CanvasRenderingContext2D,
pngFrame: PngFrame
Expand All @@ -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%;
`;
4 changes: 3 additions & 1 deletion packages/teleport/src/Player/ProgressBar/ProgressBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Slider from './Slider';
export default function ProgressBar(props: ProgressBarProps) {
const Icon = props.isPlaying ? Icons.CirclePause : Icons.CirclePlay;
return (
<StyledProgessBar>
<StyledProgessBar style={props.style} id={props.id}>
<ActionButton onClick={props.toggle}>
<Icon />
</ActionButton>
Expand Down Expand Up @@ -51,6 +51,8 @@ export type ProgressBarProps = {
current: number;
move: (value: any) => void;
toggle: () => void;
style?: React.CSSProperties;
id?: string;
};

const SliderContainer = styled.div`
Expand Down
84 changes: 84 additions & 0 deletions packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<ProgressBar
{...state}
toggle={() => playerClient.togglePlayPause()}
move={() => {}}
style={props.style}
id={props.id}
/>
);
};
3 changes: 2 additions & 1 deletion packages/teleport/src/Player/ProgressBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

0 comments on commit a18685f

Please sign in to comment.