This is a minimal set of modular and hopefully useful React components for composing media player interfaces. It is designed for you to compose media player controls yourself using a small and easy-to-learn API.
Instead of shipping default but customisable styles, there are implementation recipies to help you get going quickly. Also check out the demo site to try the components out.
⚠️ NOTE: This library does not deal with actual media in any way, only the UI. ⚠️
// ES2015+ import
import { Slider, Direction } from 'react-player-controls'
// Using CommonJS
const { Slider, Direction } = require('react-player-controls')
Any props passed to a <PlayerIcon.* /> component will be passed onto the underlying svg element.
<Slider />
The <Slider /> helps you build things like volume controls and progress bars. It does not take a value prop, but expects you to keep track of this yourself and render whatever you want inside it.
What this component actually does is that it renders an element inside itself, on top of its children, which listens to mouse events and invokes change and intent callbacks with relative, normalised values based on those events.
<Slider
direction={Direction.HORIZONTAL}
onIntent={intent => console.log(`hovered at ${intent}`)}
onIntentStart={intent => console.log(`entered with mouse at ${intent}`)}
onIntentEnd={() => console.log('left with mouse')}
onChange={newValue => console.log(`clicked at ${newValue}`)}
onChangeStart={startValue => console.log(`started dragging at ${startValue}`)}
onChangeEnd={endValue => console.log(`stopped dragging at ${endValue}`)}
>
{/* Here we render whatever we want. Nothings is rendered by default. */}
</Slider>
Prop name
Default value
Description
direction
Direction.HORIZONTAL
The slider's direction
onIntent
(intent) => {}
A function that is invoked with the relative, normalised value at which the user is hovering (when not dragging).
onIntentStart
(intent) => {}
A function this is invoked with the relative, normalised value at which the user started hovering the slider (when not dragging).
onIntentEnd
() => {}
A function this is invoked when the mouse left the slider area (when not dragging).
onChange
(value) => {}
A function that is invoked with the latest relative, normalised value that the user has set by either clicking or dragging.
onChangeStart
(value) => {}
A function that is invoked with the relative, normalised value at which the user started changing the slider's value.
onChangeEnd
(value) => {}
A function that is invoked with the relative, normalised value at which the user stopped changing the slider's value. When the component unmounts, this function will be invoked with a value of null.
children
null
Child elements.
className
null
A string to set as the HTML class attribute.
style
{}
Styles to set on the wrapping div element.
overlayZIndex
10
The z-index of the invisible overlay that captures mouse events
Recipies
Styled buttons with icons
```js
import { PlayerIcon } from 'react-player-controls'
// A base component that has base styles applied to it
const PlayerButton = ({ style, children, ...props }) => (
)
// Compose buttons with matching icons. Use whatever icon library
// you want. If you don't have any particular logic for each of the
// buttons, you might not need this abstraction.
const PlayButton = props =>
const PauseButton = props =>
const PreviousButton = props =>
const NextButton = props =>
```
Styled slider
```js
import { Direction, Slider } from 'react-player-controls'
const WHITE_SMOKE = '#eee'
const GRAY = '#878c88'
const GREEN = '#72d687'
// A colored bar that will represent the current value
const SliderBar = ({ direction, value, style }) => (
)
// A handle to indicate the current value
const SliderHandle = ({ direction, value, style }) => (
)
// A composite progress bar component
const ProgressBar = ({ isEnabled, direction, value, ...props }) => (
)
// Now use somewhere
seek(value * currentSong.duration)}
/>
```
Playback controls
```js
import Icon from 'some-icon-library'
const PlaybackControls = ({
isPlaying,
onPlaybackChange,
hasPrevious,
onPrevious,
hasNext,
onNext,
}) => (
)
// Use PlaybackControls in a player context
player.setIsPlaying(isPlaying)}
hasPrevious={songs.indexOf(currentSong) > 0}
hasNext={songs.indexOf(currentSong) < songs.length - 1}
onPrevious={player.setSong(songs[songs.indexOf(currentSong) - 1])}
onNext={player.setSong(songs[songs.indexOf(currentSong) + 1])}
/>
```
Progress bar with buffer
```js
import { Direction, Slider } from 'react-player-controls'
const Bar = ({ style, children, ...props }) => (
{children}
)
const ProgressBarWithBuffer = ({
amountBuffered,
...props,
}) => (
{/* Background bar */}
{/* Buffer bar */}
{/* Playtime bar */}
)
// Use buffer bar somewhere
```
Progress bar that shows the target time on hover
```js
import { Direction, FormattedTime, Slider } from 'react-player-controls'
// Create a basic bar that represents time
const TimeBar = ({ children }) => (
{children}
)
// Create a tooltip that will show the time
const TimeTooltip = ({ numSeconds, style = {} }) => (
)
// Create a component to keep track of user interactions
class BarWithTimeOnHover extends React.Component {
static propTypes = {
duration: PropTypes.number.isRequired,
}
constructor(props) {
super(props)
this.state = {
// This will be a normalised value between 0 and 1,
// or null when not hovered
hoverValue: null,
}
this.handleIntent = this.handleIntent.bind(this)
this.handleIntentEnd = this.handleIntentEnd.bind(this)
}
handleIntent(value) {
this.setState({
hoverValue: value,
})
}
handleIntentEnd() {
// Note that this might not be invoked if the user ends
// a control change with the mouse outside of the slider
// element, so you might want to do this inside a
// onChangeEnd callback too.
this.setState({
hoverValue: null,
})
}
render() {
const { duration } = this.props
const { hoverValue } = this.state
return (
{hoverValue !== null && (
)}
)
}
}
// Let's use it somewhere
```
Base CSS styles (as seen on the docs page)
```css
/* Root slider component */
.slider {
position: relative;
}
.slider.is-horizontal {
width: 200px;
height: 8px;
}
.slider.is-vertical {
width: 8px;
height: 200px;
}
/* Bars – can be progress. value, buffer or whatever */
.bar {
position: absolute;
border-radius: 50%;
}
.bar.is-background {
background: #878c88;
}
.bar.is-value {
background: #72d687;
}
.bar.is-horizontal {
top: 0;
bottom: 0;
left: 0;
/* width: set dynamically in js */;
height: 100%;
}
.bar.is-vertical {
right: 0;
bottom: 0;
left: 0;
width: 100%;
/* height: set dynamically in js */;
}
/* Slider handle */
.handle {
position: absolute;
width: 16px;
height: 16px;
background: 'green';
border-radius: 50%;
transform: scale(1);
transition: transform 0.2s;
}
.handle:hover {
transform: scale(1.3);
}
.handle.is-horizontal {
top: 0;
/* left: set dynamically in js to x %; */
margin-top: -4px;
margin-left: -8px;
}
.handle.is-vertical {
left: 0;
/* bottom: set dynamically in js to x %; */
margin-bottom: -8px;
margin-left: -4px;
}
```
Contribute
Contributions are very welcome, no matter your experience! Please submit a PR and we'll take it from there.