Skip to content

Instantly share code, notes, and snippets.

@trevorblades
Created October 10, 2019 03:05
Show Gist options
  • Select an option

  • Save trevorblades/63e2fb0878b86780b3eff56394022d42 to your computer and use it in GitHub Desktop.

Select an option

Save trevorblades/63e2fb0878b86780b3eff56394022d42 to your computer and use it in GitHub Desktop.
Playback play bar component
import PropTypes from 'prop-types';
import RateButton from './rate-button';
import React, {useRef, useState} from 'react';
import Waveform from './waveform';
import useHoverDirty from 'react-use/lib/useHoverDirty';
import {
Box,
IconButton,
MuiThemeProvider,
Tooltip,
Typography,
createMuiTheme
} from '@material-ui/core';
import {
MdAddCircle,
MdPause,
MdPlayArrow,
MdRemoveCircle
} from 'react-icons/md';
import {clockTime} from 'clock-time';
import {makeTheme} from 'gatsby-theme-playback';
import {withProps} from 'recompose';
const FlexWrapper = withProps({
display: 'flex',
alignItems: 'center'
})(Box);
const darkTheme = createMuiTheme(makeTheme('dark'));
export default function PlayBar(props) {
const trackRef = useRef(null);
const [seek, setSeek] = useState(0);
const isHovering = useHoverDirty(trackRef);
function handleTrackMouseDown() {
handleMouseMove(event);
props.setSeeking(true);
window.addEventListener('mousemove', handleMouseMove);
window.addEventListener('mouseup', handleMouseUp);
}
function calculateSeek(event) {
const {x} = trackRef.current.getBoundingClientRect();
const clickX = event.pageX - Math.floor(x);
const percent = clickX / trackRef.current.offsetWidth;
return Math.min(props.duration, Math.max(0, props.duration * percent));
}
function handleMouseMove(event) {
setSeek(calculateSeek(event));
}
function handleMouseUp(event) {
window.removeEventListener('mousemove', handleMouseMove);
window.removeEventListener('mouseup', handleMouseUp);
props.controls.seek(calculateSeek(event));
}
const disabled = !props.state.buffered.length;
const time = props.seeking ? seek : props.state.time;
const knobOffset = `${(time / props.duration) * 100}%`;
return (
<MuiThemeProvider theme={darkTheme}>
<Box mt="auto" position="sticky" bottom={0} bgcolor="black" color="white">
<Waveform
selectedBlock={props.selectedBlock}
editing={props.editing}
src={props.src}
knobOffset={knobOffset}
onSelectionChange={props.onSelectionChange}
duration={props.duration}
/>
<FlexWrapper
borderTop={1}
borderColor="grey.700"
px={1}
py={0.5}
style={{userSelect: 'none'}}
>
<IconButton
color="inherit"
onClick={
props.state.paused ? props.controls.play : props.controls.pause
}
disabled={disabled}
onMouseDown={event => event.preventDefault()} // prevent focus
>
<Box
component={props.state.paused ? MdPlayArrow : MdPause}
size={24}
/>
</IconButton>
<FlexWrapper flexGrow={1} mx={1}>
<Typography variant="body2">{clockTime(time * 1000)}</Typography>
<Box
flexGrow={1}
mx={1.5}
mt="-1px"
py={1}
onMouseDown={handleTrackMouseDown}
style={{cursor: 'pointer'}}
ref={trackRef}
>
<Box
height={4}
bgcolor={disabled ? 'action.disabled' : 'currentColor'}
position="relative"
>
<Box
bgcolor="primary.main"
height="100%"
style={{
transformOrigin: 'left',
transform: `scaleX(${time / props.duration})`
}}
/>
<Box
width={16}
height={16}
borderRadius="50%"
bgcolor="primary.main"
position="absolute"
top="50%"
style={{
display: isHovering || props.seeking ? 'block' : 'none',
left: knobOffset,
transform: 'translate(-50%, -50%)'
}}
/>
</Box>
</Box>
<Typography variant="body2">
{clockTime(props.duration * 1000)}
</Typography>
</FlexWrapper>
<FlexWrapper mx={1}>
<RateButton
disabled={disabled}
audioRef={props.audioRef}
value={Math.max(0.5, props.rate - 0.5)}
>
<MdRemoveCircle />
</RateButton>
<Box mx={0.5}>
<Tooltip title="Playback rate">
<Typography variant="body2">
{props.rate.toFixed(1)}x
</Typography>
</Tooltip>
</Box>
<RateButton
disabled={disabled}
audioRef={props.audioRef}
value={Math.min(2, props.rate + 0.5)}
>
<MdAddCircle />
</RateButton>
</FlexWrapper>
</FlexWrapper>
</Box>
</MuiThemeProvider>
);
}
PlayBar.propTypes = {
src: PropTypes.string.isRequired,
selectedBlock: PropTypes.object,
editing: PropTypes.bool.isRequired,
duration: PropTypes.number.isRequired,
onSelectionChange: PropTypes.func.isRequired,
audioRef: PropTypes.object.isRequired,
rate: PropTypes.number.isRequired,
controls: PropTypes.object.isRequired,
state: PropTypes.object.isRequired,
seeking: PropTypes.bool.isRequired,
setSeeking: PropTypes.func.isRequired
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment