{"id":19531486,"url":"https://github.com/alexanderwallin/react-player-controls","last_synced_at":"2025-04-04T14:07:34.300Z","repository":{"id":9091590,"uuid":"60839690","full_name":"alexanderwallin/react-player-controls","owner":"alexanderwallin","description":"⏯ Dumb and (re)useful React components for media players.","archived":false,"fork":false,"pushed_at":"2023-03-26T19:25:39.000Z","size":2794,"stargazers_count":190,"open_issues_count":20,"forks_count":35,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-28T13:08:05.505Z","etag":null,"topics":["media-player","player-controls","react","react-components"],"latest_commit_sha":null,"homepage":"http://alexanderwallin.github.io/react-player-controls/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"isc","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexanderwallin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-06-10T10:19:54.000Z","updated_at":"2025-01-24T01:59:08.000Z","dependencies_parsed_at":"2024-06-18T13:49:47.438Z","dependency_job_id":"532ca8be-6343-447d-a680-99985f6496ff","html_url":"https://github.com/alexanderwallin/react-player-controls","commit_stats":{"total_commits":282,"total_committers":12,"mean_commits":23.5,"dds":"0.13829787234042556","last_synced_commit":"f49bb4d9a189df424ff3220abceb4dfb77df5afe"},"previous_names":[],"tags_count":35,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderwallin%2Freact-player-controls","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderwallin%2Freact-player-controls/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderwallin%2Freact-player-controls/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexanderwallin%2Freact-player-controls/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexanderwallin","download_url":"https://codeload.github.com/alexanderwallin/react-player-controls/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247190250,"owners_count":20898702,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["media-player","player-controls","react","react-components"],"created_at":"2024-11-11T01:43:07.594Z","updated_at":"2025-04-04T14:07:34.279Z","avatar_url":"https://github.com/alexanderwallin.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg src=\"https://alexanderwallin.github.io/react-player-controls/assets/logo-icon.svg\" width=\"100\" height=\"100\" /\u003e\n  \u003cbr /\u003e\n  react-player-controls\n  \u003cbr /\u003e\n  \u0026nbsp;\n\u003c/h1\u003e\n\n[![npm version](https://badge.fury.io/js/react-player-controls.svg)](https://npmjs.com/package/react-player-controls)\n[![Build Status](https://travis-ci.org/alexanderwallin/react-player-controls.svg?branch=master)](https://travis-ci.org/alexanderwallin/react-player-controls)\n[![Dependencies](https://img.shields.io/david/alexanderwallin/react-player-controls.svg?style=flat-square)](https://david-dm.org/alexanderwallin/react-player-controls)\n[![Dev dependency status](https://david-dm.org/alexanderwallin/react-player-controls/dev-status.svg?style=flat-square)](https://david-dm.org/alexanderwallin/react-player-controls#info=devDependencies)\n\nThis 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](#api).\n\nInstead of shipping default but customisable styles, there are [implementation recipies](#recipes) to help you get going quickly. Also check out [the demo site](http://alexanderwallin.github.io/react-player-controls/) to try the components out.\n\n⚠️  **NOTE:** This library does not deal with actual media in any way, only the UI. ⚠️\n\n## Table of contents\n\n* [Installation](#installation)\n* [Usage](#usage)\n* [API](#api)\n* [Recipies](#recipies)\n* [Contribute](#contribute)\n\n\n## Installation\n\n```sh\nnpm i react-player-controls\n```\n\n## Usage\n\n```js\n// ES2015+ import\nimport { Slider, Direction } from 'react-player-controls'\n\n// Using CommonJS\nconst { Slider, Direction } = require('react-player-controls')\n```\n\n## API\n\n* [`Direction`](#direction)\n* [`\u003cFormattedTime /\u003e`](#formattedtime-)\n* [`\u003cPlayerIcon /\u003e`](#playericon-)\n* [`\u003cSlider /\u003e`](#slider-)\n\n### `Direction`\n\nAn enum describing a slider's active axis.\n\n| Key | Value |\n|-----|-------|\n| `HORIZONTAL` | `\"HORIZONTAL\"` |\n| `VERTICAL` | `\"VERTICAL\"` |\n\n### `\u003cFormattedTime /\u003e`\n\n`\u003cFormattedTime /\u003e` translates a number of seconds into the player-friendly format of `m:ss`, or `h:mm:ss` if the total time is one hour or higher.\n\n```js\n// This will render -1:01:02\n\u003cFormattedTime numSeconds={-3662} /\u003e\n```\n\n| Prop name | Default value | Description |\n|-----------|---------------|-------------|\n| `numSeconds` | `0` | A number of seconds, positive or negative |\n| `className` | `null` | A string to set as the HTML `class` attribute |\n| `style` | `{}` | Styles to set on the wrapping `span` element. |\n\n### `\u003cPlayerIcon /\u003e`\n\n`\u003cPlayerIcon /\u003e` is not really a component in itself, but a container of a number of icon components.\n\n```js\n\u003cPlayerIcon.Play /\u003e\n\u003cPlayerIcon.Pause /\u003e\n\u003cPlayerIcon.Previous /\u003e\n\u003cPlayerIcon.Next /\u003e\n\u003cPlayerIcon.SoundOn /\u003e\n\u003cPlayerIcon.SoundOff /\u003e\n```\n\nAny props passed to a `\u003cPlayerIcon.* /\u003e` component will be passed onto the underlying `svg` element.\n\n### `\u003cSlider /\u003e`\n\nThe `\u003cSlider /\u003e` 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.\n\nWhat 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.\n\n```js\n\u003cSlider\n  direction={Direction.HORIZONTAL}\n  onIntent={intent =\u003e console.log(`hovered at ${intent}`)}\n  onIntentStart={intent =\u003e console.log(`entered with mouse at ${intent}`)}\n  onIntentEnd={() =\u003e console.log('left with mouse')}\n  onChange={newValue =\u003e console.log(`clicked at ${newValue}`)}\n  onChangeStart={startValue =\u003e console.log(`started dragging at ${startValue}`)}\n  onChangeEnd={endValue =\u003e console.log(`stopped dragging at ${endValue}`)}\n\u003e\n  {/* Here we render whatever we want. Nothings is rendered by default. */}\n\u003c/Slider\u003e\n```\n\n| Prop name | Default value | Description |\n|-----------|---------------|-------------|\n| `direction` | `Direction.HORIZONTAL` | The slider's direction |\n| `onIntent` | `(intent) =\u003e {}` | A function that is invoked with the relative, normalised value at which the user is hovering (when not dragging). |\n| `onIntentStart` | `(intent) =\u003e {}` | A function this is invoked with the relative, normalised value at which the user started hovering the slider (when not dragging). |\n| `onIntentEnd` | `() =\u003e {}` | A function this is invoked when the mouse left the slider area (when not dragging). |\n| `onChange` | `(value) =\u003e {}` | A function that is invoked with the latest relative, normalised value that the user has set by either clicking or dragging. |\n| `onChangeStart` | `(value) =\u003e {}` | A function that is invoked with the relative, normalised value at which the user started changing the slider's value. |\n| `onChangeEnd` | `(value) =\u003e {}` | 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`. |\n| `children` | `null` | Child elements. |\n| `className` | `null` | A string to set as the HTML `class` attribute. |\n| `style` | `{}` | Styles to set on the wrapping `div` element. |\n| `overlayZIndex` | 10 | The `z-index` of the invisible overlay that captures mouse events |\n\n\n## Recipies\n\n\u003cdetails\u003e\n\u003csummary\u003eStyled buttons with icons\u003c/summary\u003e\n\n```js\nimport { PlayerIcon } from 'react-player-controls'\n\n// A base component that has base styles applied to it\nconst PlayerButton = ({ style, children, ...props }) =\u003e (\n  \u003cbutton\n    style={{\n      appearance: 'none',\n      outline: 'none',\n      border: 'none',\n      borderRadius: 3,\n      background: 'white',\n      color: 'blue',\n      '\u0026:hover': {\n        'color': 'lightblue',\n      },\n      ...style,\n    }}\n    {...props}\n  \u003e\n    {children}\n  \u003c/button\u003e\n)\n\n// Compose buttons with matching icons. Use whatever icon library\n// you want. If you don't have any particular logic for each of the\n// buttons, you might not need this abstraction.\nconst PlayButton = props =\u003e \u003cbutton {...props}\u003e\u003cPlayerIcon.Play /\u003e\u003c/button\u003e\nconst PauseButton = props =\u003e \u003cbutton {...props}\u003e\u003cPlayerIcon.Pause /\u003e\u003c/button\u003e\nconst PreviousButton = props =\u003e \u003cbutton {...props}\u003e\u003cPlayerIcon.Previous /\u003e\u003c/button\u003e\nconst NextButton = props =\u003e \u003cbutton {...props}\u003e\u003cPlayerIcon.Next /\u003e\u003c/button\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eStyled slider\u003c/summary\u003e\n\n```js\nimport { Direction, Slider } from 'react-player-controls'\n\nconst WHITE_SMOKE = '#eee'\nconst GRAY = '#878c88'\nconst GREEN = '#72d687'\n\n// A colored bar that will represent the current value\nconst SliderBar = ({ direction, value, style }) =\u003e (\n  \u003cdiv\n    style={Object.assign({}, {\n      position: 'absolute',\n      background: GRAY,\n      borderRadius: 4,\n    }, direction === Direction.HORIZONTAL ? {\n      top: 0,\n      bottom: 0,\n      left: 0,\n      width: `${value * 100}%`,\n    } : {\n      right: 0,\n      bottom: 0,\n      left: 0,\n      height: `${value * 100}%`,\n    }, style)}\n  /\u003e\n)\n\n// A handle to indicate the current value\nconst SliderHandle = ({ direction, value, style }) =\u003e (\n  \u003cdiv\n    style={Object.assign({}, {\n      position: 'absolute',\n      width: 16,\n      height: 16,\n      background: GREEN,\n      borderRadius: '100%',\n      transform: 'scale(1)',\n      transition: 'transform 0.2s',\n      '\u0026:hover': {\n        transform: 'scale(1.3)',\n      }\n    }, direction === Direction.HORIZONTAL ? {\n      top: 0,\n      left: `${value * 100}%`,\n      marginTop: -4,\n      marginLeft: -8,\n    } : {\n      left: 0,\n      bottom: `${value * 100}%`,\n      marginBottom: -8,\n      marginLeft: -4,\n    }, style)}\n  /\u003e\n)\n\n// A composite progress bar component\nconst ProgressBar = ({ isEnabled, direction, value, ...props }) =\u003e (\n  \u003cSlider\n    direction={direction}\n    onChange={/* store value somehow */}\n    style={{\n      width: direction === Direction.HORIZONTAL ? 200 : 8,\n      height: direction === Direction.HORIZONTAL ? 8 : 130,\n      borderRadius: 4,\n      background: WHITE_SMOKE,\n      transition: direction === Direction.HORIZONTAL ? 'width 0.1s' : 'height 0.1s',\n      cursor: isEnabled === true ? 'pointer' : 'default',\n    }}\n    {...props}\n  \u003e\n    \u003cSliderBar direction={direction} value={value} style={{ background: isEnabled ? GREEN : GRAY }} /\u003e\n    \u003cSliderHandle direction={direction} value={value} style={{ background: isEnabled ? GREEN : GRAY }} /\u003e\n  \u003c/Slider\u003e\n)\n\n// Now use \u003cProgressBar /\u003e somewhere\n\u003cProgressBar\n  isEnabled\n  direction={Direction.HORIZONTAL}\n  value={currentTime / currentSong.duration}\n  onChange={value =\u003e seek(value * currentSong.duration)}\n/\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003ePlayback controls\u003c/summary\u003e\n\n```js\nimport Icon from 'some-icon-library'\n\nconst PlaybackControls = ({\n  isPlaying,\n  onPlaybackChange,\n  hasPrevious,\n  onPrevious,\n  hasNext,\n  onNext,\n}) =\u003e (\n  \u003cdiv\u003e\n    \u003cbutton disabled={!hasPrevious} onClick={onPrevious}\u003e\n      \u003cIcon.Previous /\u003e\n    \u003c/button\u003e\n\n    \u003cbutton onClick={() =\u003e onPlaybackChange(!isPlaying)}\u003e\n      {isPlaying ? \u003cIcon.Pause /\u003e : \u003cIcon.Play /\u003e}\n    \u003c/button\u003e\n\n    \u003cbutton disabled={!hasNext} onClick={onNext}\u003e\n      \u003cIcon.Next /\u003e\n    \u003c/button\u003e\n  \u003c/div\u003e\n)\n\n// Use PlaybackControls in a player context\n\u003cPlaybackControls\n  isPlaying={player.isPlaying}\n  onPlaybackChange={isPlaying =\u003e player.setIsPlaying(isPlaying)}\n  hasPrevious={songs.indexOf(currentSong) \u003e 0}\n  hasNext={songs.indexOf(currentSong) \u003c songs.length - 1}\n  onPrevious={player.setSong(songs[songs.indexOf(currentSong) - 1])}\n  onNext={player.setSong(songs[songs.indexOf(currentSong) + 1])}\n/\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eProgress bar with buffer\u003c/summary\u003e\n\n```js\nimport { Direction, Slider } from 'react-player-controls'\n\nconst Bar = ({ style, children, ...props }) =\u003e (\n  \u003cdiv\n    style={{\n      height: 6,\n      width: '100%',\n      ...style,\n    }}\n  \u003e\n    {children}\n  \u003c/div\u003e\n)\n\nconst ProgressBarWithBuffer = ({\n  amountBuffered,\n  ...props,\n}) =\u003e (\n  \u003cSlider\n    direction={Direction.HORIZONTAL}\n    {...props}\n  \u003e\n    {/* Background bar */}\n    \u003cBar style={{ background: 'gray', width: '100%' }} /\u003e\n\n    {/* Buffer bar */}\n    \u003cBar style={{ background: 'silver', width: `${amountBuffered * 100}%` }} /\u003e\n\n    {/* Playtime bar */}\n    \u003cBar style={{ background: 'blue', width: `${100 * currentTime / duration}%` }} /\u003e\n  \u003c/Slider\u003e\n)\n\n// Use buffer bar somewhere\n\u003cProgressBarWithBuffer\n  amountBuffered={secondsBuffered / duration}\n  {/* callback props etc */}\n/\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eProgress bar that shows the target time on hover\u003c/summary\u003e\n\n```js\nimport { Direction, FormattedTime, Slider } from 'react-player-controls'\n\n// Create a basic bar that represents time\nconst TimeBar = ({ children }) =\u003e (\n  \u003cdiv\n    style={{\n      height: 6,\n      width: '100%',\n      background: 'gray',\n    }}\n  \u003e\n    {children}\n  \u003c/div\u003e\n)\n\n// Create a tooltip that will show the time\nconst TimeTooltip = ({ numSeconds, style = {} }) =\u003e (\n  \u003cdiv\n    style={{\n      display: 'inline-block',\n      position: 'absolute',\n      bottom: '100%',\n      transform: 'translateX(-50%)',\n      padding: 8,\n      borderRadius: 3,\n      background: 'darkblue',\n      color: 'white',\n      fontSize: 12,\n      fontWeight: 'bold',\n      lineHeight: 16,\n      textAlign: 'center',\n      ...style,\n    }}\n  \u003e\n    \u003cFormattedTime numSeconds={numSeconds} /\u003e\n  \u003c/div\u003e\n)\n\n// Create a component to keep track of user interactions\nclass BarWithTimeOnHover extends React.Component {\n  static propTypes = {\n    duration: PropTypes.number.isRequired,\n  }\n\n  constructor(props) {\n    super(props)\n\n    this.state = {\n      // This will be a normalised value between 0 and 1,\n      // or null when not hovered\n      hoverValue: null,\n    }\n\n    this.handleIntent = this.handleIntent.bind(this)\n    this.handleIntentEnd = this.handleIntentEnd.bind(this)\n  }\n\n  handleIntent(value) {\n    this.setState({\n      hoverValue: value,\n    })\n  }\n\n  handleIntentEnd() {\n    // Note that this might not be invoked if the user ends\n    // a control change with the mouse outside of the slider\n    // element, so you might want to do this inside a\n    // onChangeEnd callback too.\n    this.setState({\n      hoverValue: null,\n    })\n  }\n\n  render() {\n    const { duration } = this.props\n    const { hoverValue } = this.state\n\n    return (\n      \u003cSlider\n        direction={Direction.HORIZONTAL}\n        style={{\n          position: 'relative',\n        }}\n        onIntent={this.handleIntent}\n        onIntentEnd={this.handleIntentEnd}\n      \u003e\n        \u003cTimeBar /\u003e\n\n        {hoverValue !== null \u0026\u0026 (\n          \u003cTimeTooltip\n            numSeconds={hoverValue * duration}\n            style={{\n              left: `${hoverValue * 100}%`,\n            }}\n          /\u003e\n        )}\n      \u003c/Slider\u003e\n    )\n  }\n}\n\n// Let's use it somewhere\n\u003cBarWithTimeOnHover duration={video.duration} /\u003e\n```\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003eBase CSS styles (as seen on the docs page)\u003c/summary\u003e\n\n```css\n/* Root slider component */\n.slider {\n  position: relative;\n}\n\n.slider.is-horizontal {\n  width: 200px;\n  height: 8px;\n}\n\n.slider.is-vertical {\n  width: 8px;\n  height: 200px;\n}\n\n/* Bars – can be progress. value, buffer or whatever */\n.bar {\n  position: absolute;\n  border-radius: 50%;\n}\n\n.bar.is-background {\n  background: #878c88;\n}\n\n.bar.is-value {\n  background: #72d687;\n}\n\n.bar.is-horizontal {\n  top: 0;\n  bottom: 0;\n  left: 0;\n  /* width: set dynamically in js */;\n  height: 100%;\n}\n\n.bar.is-vertical {\n  right: 0;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  /* height: set dynamically in js */;\n}\n\n/* Slider handle */\n.handle {\n  position: absolute;\n  width: 16px;\n  height: 16px;\n  background: 'green';\n  border-radius: 50%;\n  transform: scale(1);\n  transition: transform 0.2s;\n}\n\n.handle:hover {\n  transform: scale(1.3);\n}\n\n.handle.is-horizontal {\n  top: 0;\n  /* left: set dynamically in js to x %; */\n  margin-top: -4px;\n  margin-left: -8px;\n}\n\n.handle.is-vertical {\n  left: 0;\n  /* bottom: set dynamically in js to x %; */\n  margin-bottom: -8px;\n  margin-left: -4px;\n}\n```\n\u003c/details\u003e\n\n\n## Contribute\n\nContributions are very welcome, no matter your experience! Please submit a PR and we'll take it from there.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderwallin%2Freact-player-controls","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexanderwallin%2Freact-player-controls","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexanderwallin%2Freact-player-controls/lists"}