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

Maintain aspect ratio on Desktop Playback #635

Merged
merged 4 commits into from
Mar 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to run this logic on every PNG frame? Or can we instead run it once when we initialize the player and (optionally) run it in a window resize handler?

Copy link
Author

@ibeckermayer ibeckermayer Mar 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's only being run when we receive the client screen spec tdp message, so since we currently don't support a dynamic resize, it just happens once at the beginning. Once we do implement resize, this will also account for that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh! Github's UI confuses me, I looked up and it looked like this was inside tdpCliOnPngFrame, but it's not. My eyes always glance over the light blue bars. Sorry!

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 };