ps-hackathon-video-player
v0.0.1-test
Published
## Install
Downloads
1
Readme
Video Player
Install
yarn add @a-cloud-guru/video-player
Usage
There's a few examples of usage:
examples/create-react-app
src/__stories__/Player.stories.js
The player is made of two components: PlayerProvider
and Player
, the idea being to wrap the Player in a PlayerProvider:
const Component = () => (
<PlayerProvider sources={sources}>
<Player />
</PlayerProvider>
)
where sources looks like:
const sources = {
'360p': "http://source",
'720p': "http://source",
// whatever you want here
'quality': "src",
}
We also expose a usePlayer
hook, allowing a component to access the player state and a few actions:
// player-info.js
export const PlayerInfo = () => {
const [{ playing }, { play, pause }] = usePlayer();
return (
<div>
<p>Player is {playing ? 'playing' : 'paused'}.</p>
<button onClick={playing ? pause : play}>{playing ? 'Pause' : 'Play'}</button>
</div>
)
}
// another-component.js
import { PlayerInfo } from './player-info';
<PlayerProvider sources={sources}>
<Player />
<PlayerInfo />
</PlayerProvider>
List of properties accessible through usePlayer
const [state, actions] = usePlayer();
--STATE-- | property | default value | description | | --- | --- | --- | | pip | false | is picture in picture enabled? | muted | false | is the audio muted? | controlBarVisible | false | is the control bar visible? | settingsVisible | false | are the settings visibile? | autoplay | true | is autoplay on? | volume | 1 | current volume value | playing | false | is the video playing? | loading | true | is the video still loading? | seeking | false | are we currently seeking forward/backward? | durationSeconds | 0 | total length of the video | playedSeconds | 0 | current position in the video | interval | { back:15, forward:15, } | number of seconds to seek backward/forward | subtitleEnabled | false | are subtitles enabled? | quality | '720p' | current quality | playbackRate | 1 | current playback rate | completedTimerCallbacks | [] | list of timer callbacks we've already called; avoids triggering them twice | fullscreen | false | is the player fullscreen? | sources | undefined | array of sources; refer to the next section for details | subtitle | undefined | subtitle url; refer to the next section for details | currentCueText | '' | current subtitle text | url | sources[quality] || sources.Auto | current url | compact | false | is the player in "compact" mode? more on that in the next section | hidden | false | is the player "hidden"? more on that in the next section
--ACTIONS-- | property | type | description | | --- | --- | --- | | reset | function | reset the video player to its initial state | | play | function | play the video | | pause | function | pause the video | | togglePlay | function | play/pause helper | | prev | function | trigger the "onPrevious" callback | | next | function | trigger the "onNext" callback | | playAgain | function | seek back to the beginning of the video | | mute | function | mute the audio | | unmute | function | unmute the audio | | toggleMute | function | mute/unmute helper | | updateVolume | function | update the volume to a new value | | seekTo | function | move to a specific second in the video | | forward | function | seek forward state.interval.forward seconds | | backward | function | seek backward state.interval.backward seconds | | fullScreen | function | enables fullscreen | | exitFullScreen | function | disables fullscreen | | updatePlaybackRate | function | change the playback rate | | updateSeekInterval | function | call it with an object containing {forward:seconds} or {backward:seconds} to update the seek interval | | updateQuality | function | update the quality of the video; effectively switches the source | | toggleAutoplay | function | enable/disable autoplay | | togglePiP | function | enable/disable picture in picture | | canEnablePiP | function | is PiP supported by the browser? call it with the url for the current source | | toggleSubtitle | function | turn subtitles on/off | | canEnableSubtitle | boolean | do we have subtitles? | | player | React ref | react reference to the video player element |
List of properties on the PlayerProvider
| property | type | description |
| --- | --- | --- |
| sources | object | as described above, this contains a list of quality/source pairs. This is the only actually required field. const sources = { '360p': "http://source", '720p': "http://source", 'quality': "src", }
| subtitle | string | an url to a subtitles file.
| getPlayerContainer | function | used to get a reference to the container parent. Used to handle onClick events for example. Example: () => document.body
.
| hidden | boolean | because mounting the player on the page starts loading the video, it's sometimes useful to put the player on the page before it's actually visibile. Using this parameter, we can mount the component, but hide the player and disable a few things which would be disruptive, like the keyboard bindings for example.
| compact | boolean | used to display a smaller version of the video player – currently unstable
| showFullControls | boolean | whether to display "next" and "previous" buttons in the control bar
| onPrevious | function | triggered when prev()
is called.
| onError | function | triggered when there's an error trying to play the video, as defined by react-player
here.
| onNext | function | triggered when next()
is called.
| keyProp | string | a value that is used as a key on the underlying component. Useful to re-render the whole player when changing videos for example. Example: ${courseId}-${lessonId}
.
| onUpdateProgress | function | called by onProgress
, as defined by react-player
here
| onControlBar | object | contains a list of callbacks (functions), triggered in various occasions. More on that later.
| timerCallbacks | array | a list of callbacks to be called at very specific time points in the video. It's been designed to be as flexible as possible, more on that later.
| trackFnRef | React ref | a react reference to a function; essentially a reference where current
is a function, called every time the player wants to track a "watched" event.
| maxTimeOfNoActivity | number, milliseconds | Defaults to 5000 (5 seconds). When a user pauses the video, this is the delay after which we send the event through the track function.
| maxTimeThreshold | number, milliseconds | Defaults to 270000 (270 seconds, or 4m30s). While the video is still playing, this is the delay after which we send an event through the track function
--ON CONTROL BAR--
These callbacks are available in the property mentioned in the previous section, called onControlBar
.
| property | type | description |
| --- | --- | --- |
| onPlay | function | called before the playback is resumed, using play()
| onPause | function | called before the playback is paused, using pause()
| onAutoPlayChange | function | called before toggling the autoplay, using toggleAutoplay()
. The value is a boolean representing the active state.
| onPiPChange | function | called before toggling picture in picture, using togglePip()
. The value is a boolean representing the active state.
| onPlaybackRateChange | function | called before changing the speed of the playback, using updatePlaybackRate()
. The value is the new playback rate.
| onSeekForward | function | called before seeking forward in the video, using forward()
. The value is an object of the following shape { from: where_we_were_in_seconds, to: where_we_moved_in_seconds }
| onSeekBackward | function | called before seeking backward in the video, using backward()
. The value is an object of the following shape { from: where_we_were_in_seconds, to: where_we_moved_in_seconds }
| onToggleSubtitle | function | called before toggling the subtitles, using toggleSubtitle()
. The value is a boolean representing the active state.
| onSendFeedback | function | called when the "send feedback" button is clicked
--ABOUT TIMER CALLBACKS--
const timerCallbacks = [
{
type: "start",
second: 23,
callback: () => console.log("called at 23s from the start")
},
{
type: "start",
percent: 0.3,
callback: () => console.log("called at 30% of the video")
},
{
type: "end",
second: 10,
callback: () => console.log("called at 10s before the end")
},
{
type: "end",
percent: 20,
callback: () => console.log("called at 20% from the end / 80% of the video")
}
]
--VIDEO TRACKING--
- when a user starts watching a video, every 270s (4min 30s), we send an event – you can think of it as some sort of heartbeat.
- if the user pauses the video, we send the event after 5 seconds, to avoid being spammed by double clicks on the play button).
- if a user is watching a video, doesn’t reach the 270s required to trigger the event, but closes their browser/computer/tab/etc, we just keep a record in their browser’s local storage – when they come back to our website, the event we stored here will get sent, so that we don’t lose the time they spent watching in their previous session.
--TRACKING EVENTS--
| property | type | description | | --- | --- | --- | | actualTime | number, in milliseconds | this is the actual time, as in the time that the user spends in front of the video. | contentTime | number, in seconds | this is the content time counterpart – the duration of video content the user has watched. Note that actualTime and contentTime can be differ because of the playback speed. | from | number, in seconds | when did we start tracking for the current event? | to | number, in seconds | when did we stop tracking for the current event? | trackedLater | boolean | whether it’s an event that we could not track because the user moved away from the app, so we tracked it when they came back. | originalTrackDate | timestamp | this is a timestamp, corresponding to when we recorded the event. If the event is tracked right away, this will be very close to when we actually register the event in Segment, but when it’s tracked after the user has left the app and come back (when trackedLater = true), this originalTrackDate will hold the date at which we recorded the event | currentPlaybackRate | number | the speed at which the video is played | currentSubtitleEnabled | boolean | whether the subtitles are on or off | currentPip | boolean | true if the user is watching the video in Picture-in-Picture | currentQuality | string | the quality the user is watching the video at
List of properties on the Player component
| property | type | description |
| --- | --- | --- |
| containerStyle | object | spread in the sx
prop associated with the player container component (more about it here, it's essentially react styles)
| controlBarStyle | object | spread in the sx
prop associated with the control bar component
| onEndedCallback | function | a hook allowing the caller to precisely control what to do at the end of the video.
--ON ENDED CALLBACK--
We've used this to show a popup at the end of the video, only if a set of conditions is fulfilled. Implementation details and example:
const onEndedCallback = ({defaultOnEnd}) => {
if (Math.random() > 0.5) {
// do something here, show a popup, etc
// ...
} else {
defaultOnEnd();
}
}
// implementation details inside Player.js
<ReactPlayer
// onEnded triggered by react-player, as described here: https://github.com/CookPete/react-player#callback-props
onEnded={() => {
const defaultOnEnd = () => autoplay && next() && onAutoplay();
// if onEndedCallback is provided, call it with defaultOnEnd as a param
return onEndedCallback
? onEndedCallback({ pip, fullscreen, defaultOnEnd, durationSeconds })
// otherwise just call defaultOnEnd
: defaultOnEnd();
}}
/>
TODO
- consider decoupling tracking; should it be in the base player or in a wrapper?
- fix the disabled linting rules