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

Commit

Permalink
smooth out progress bar (#648)
Browse files Browse the repository at this point in the history
* smooths out progress bar

* refactors updateCurrentTime to be able to be used more flexibly, removes the WS_CLOSE listener (see https://github.com/gravitational/webapps/issues/649)
  • Loading branch information
Isaiah Becker-Mayer authored Mar 4, 2022
1 parent fc24f65 commit 278baa5
Showing 1 changed file with 65 additions and 29 deletions.
94 changes: 65 additions & 29 deletions packages/teleport/src/Player/ProgressBar/ProgressBarDesktop.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';

import { throttle } from 'lodash';
import { dateToUtc } from 'shared/services/loc';
Expand All @@ -19,6 +19,7 @@ export const ProgressBarDesktop = (props: {
id?: string;
}) => {
const { playerClient, durationMs } = props;
const intervalRef = useRef<NodeJS.Timer>();

const toHuman = (currentMs: number) => {
return format(dateToUtc(new Date(currentMs)), 'mm:ss');
Expand All @@ -32,56 +33,91 @@ export const ProgressBarDesktop = (props: {
isPlaying: true, // determines whether play or pause symbol is shown
});

// updateCurrentTime is a helper function to update the state variable.
// It should be used within a setState, like
// setState(prevState => {
// return updateCurrentTime(prevState, newTime)
// })
const updateCurrentTime = (
prevState: typeof state,
currentTimeMs: number
) => {
return {
...prevState,
current: currentTimeMs,
time: toHuman(currentTimeMs),
};
};

useEffect(() => {
if (playerClient) {
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 };
});
});
// Starts the smoothing interval, which smooths out the progress of the progress bar.
// This ensures the bar continues to progress even during playbacks where there are long
// intervals between TDP events sent to us by the server. The interval should be active
// whenever the playback is in "play" mode.
const smoothOutProgress = () => {
const smoothingInterval = 25;

intervalRef.current = setInterval(() => {
setState(prevState => {
const nextTimeMs = prevState.current + smoothingInterval;
if (nextTimeMs <= durationMs) {
return updateCurrentTime(prevState, nextTimeMs);
} else {
stopProgress();
return updateCurrentTime(prevState, durationMs);
}
});
}, smoothingInterval);
};

// The player always starts in play mode, so call this initially.
smoothOutProgress();

// Clears the smoothing interval and cancels any throttled updates,
// should be called when the playback is paused or ended.
const stopProgress = () => {
throttledUpdateCurrentTime.cancel();
clearInterval(intervalRef.current);
};

const throttledUpdateCurrentTime = throttle(
currentTimeMs => {
setState(prevState => {
return {
...prevState,
current: currentTimeMs,
time: toHuman(currentTimeMs),
};
return updateCurrentTime(prevState, currentTimeMs);
});
},
// Magic number to throttle progress bar updates so that the playback is smoother.
// Magic number to throttle progress bar updates caused by TDP events
// so that the playback is smoother.
50
);

// Listens for UPDATE_CURRENT_TIME events which coincide with
// TDP events sent to the playerClient by the server.
playerClient.addListener(
PlayerClientEvent.UPDATE_CURRENT_TIME,
currentTimeMs => throttledUpdateCurrentTime(currentTimeMs)
);

const progressToEnd = () => {
throttledUpdateCurrentTime.cancel();
// TODO(isaiah): Make this smoother
// https://github.com/gravitational/webapps/issues/579
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, current: durationMs };
if (prevState.isPlaying) {
// pause
stopProgress();
} else {
// play
smoothOutProgress();
}
return { ...prevState, isPlaying: !prevState.isPlaying };
});
};

playerClient.addListener(PlayerClientEvent.SESSION_END, () => {
progressToEnd();
});

playerClient.addListener(TdpClientEvent.TDP_ERROR, () => {
progressToEnd();
});

return () => {
throttledUpdateCurrentTime.cancel();
playerClient.nuke();
stopProgress();
};
}
}, [playerClient]);
Expand Down

0 comments on commit 278baa5

Please sign in to comment.