@dotconnor/grommet
Version:
focus on the essential experience
468 lines (415 loc) • 15 kB
JavaScript
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
import React, { forwardRef, useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { ThemeContext } from 'styled-components';
import { defaultProps } from '../../default-props';
import { Box } from '../Box';
import { Button } from '../Button';
import { Menu } from '../Menu';
import { Meter } from '../Meter';
import { Stack } from '../Stack';
import { Text } from '../Text';
import { containsFocus, useForwardedRef } from '../../utils';
import { StyledVideo, StyledVideoContainer, StyledVideoControls, StyledVideoScrubber } from './StyledVideo'; // Split the volume control into 6 segments. Empirically determined.
var VOLUME_STEP = 0.166667;
var formatTime = function formatTime(time) {
var minutes = Math.round(time / 60);
if (minutes < 10) {
minutes = "0" + minutes;
}
var seconds = Math.round(time) % 60;
if (seconds < 10) {
seconds = "0" + seconds;
}
return minutes + ":" + seconds;
};
var Video = /*#__PURE__*/forwardRef(function (_ref, ref) {
var alignSelf = _ref.alignSelf,
autoPlay = _ref.autoPlay,
children = _ref.children,
_ref$controls = _ref.controls,
controls = _ref$controls === void 0 ? 'over' : _ref$controls,
gridArea = _ref.gridArea,
loop = _ref.loop,
margin = _ref.margin,
messages = _ref.messages,
mute = _ref.mute,
_onDurationChange = _ref.onDurationChange,
_onEnded = _ref.onEnded,
_onPause = _ref.onPause,
_onPlay = _ref.onPlay,
_onTimeUpdate = _ref.onTimeUpdate,
_onVolumeChange = _ref.onVolumeChange,
rest = _objectWithoutPropertiesLoose(_ref, ["alignSelf", "autoPlay", "children", "controls", "gridArea", "loop", "margin", "messages", "mute", "onDurationChange", "onEnded", "onPause", "onPlay", "onTimeUpdate", "onVolumeChange"]);
var theme = useContext(ThemeContext) || defaultProps.theme;
var _useState = useState([]),
captions = _useState[0],
setCaptions = _useState[1];
var _useState2 = useState(),
currentTime = _useState2[0],
setCurrentTime = _useState2[1];
var _useState3 = useState(),
duration = _useState3[0],
setDuration = _useState3[1];
var _useState4 = useState(),
percentagePlayed = _useState4[0],
setPercentagePlayed = _useState4[1];
var _useState5 = useState(false),
playing = _useState5[0],
setPlaying = _useState5[1];
var _useState6 = useState(),
scrubTime = _useState6[0],
setScrubTime = _useState6[1];
var _useState7 = useState(),
volume = _useState7[0],
setVolume = _useState7[1];
var _useState8 = useState(false),
hasPlayed = _useState8[0],
setHasPlayed = _useState8[1];
var _useState9 = useState(),
interacting = _useState9[0],
setInteracting = _useState9[1];
var _useState10 = useState(),
height = _useState10[0],
setHeight = _useState10[1];
var _useState11 = useState(),
width = _useState11[0],
setWidth = _useState11[1];
var containerRef = useRef();
var scrubberRef = useRef();
var videoRef = useForwardedRef(ref); // mute if needed
useEffect(function () {
var video = videoRef.current;
if (video && mute) video.muted = true;
}, [mute, videoRef]); // when the video is first rendered, set state from it where needed
useEffect(function () {
var video = videoRef.current;
if (video) {
// hide all captioning to start with
var textTracks = video.textTracks;
for (var i = 0; i < textTracks.length; i += 1) {
textTracks[i].mode = 'hidden';
}
setCurrentTime(video.currentTime);
setPercentagePlayed(video.currentTime / video.duration * 100);
setVolume(videoRef.current.volume);
}
}, [videoRef]); // turn off interacting after a while
useEffect(function () {
var timer = setTimeout(function () {
if (interacting && !containsFocus(containerRef.current)) {
setInteracting(false);
}
}, 3000);
return function () {
return clearTimeout(timer);
};
}, [interacting]);
useLayoutEffect(function () {
var video = videoRef.current;
if (video) {
if (video.videoHeight) {
// set the size based on the video aspect ratio
var rect = video.getBoundingClientRect();
var ratio = rect.width / rect.height;
var videoRatio = video.videoWidth / video.videoHeight;
if (videoRatio > ratio) {
var nextHeight = rect.width / videoRatio;
if (nextHeight !== height) {
setHeight(nextHeight);
setWidth(undefined);
}
} else {
var nextWidth = rect.height * videoRatio;
if (nextWidth !== width) {
setHeight(undefined);
setWidth(nextWidth);
}
}
} // remember the state of the text tracks for subsequent rendering
var textTracks = video.textTracks;
if (textTracks.length > 0) {
if (textTracks.length === 1) {
var active = textTracks[0].mode === 'showing';
if (!captions || !captions[0] || captions[0].active !== active) {
setCaptions([{
active: active
}]);
}
} else {
var nextCaptions = [];
var set = false;
for (var i = 0; i < textTracks.length; i += 1) {
var track = textTracks[i];
var _active = track.mode === 'showing';
nextCaptions.push({
label: track.label,
active: _active
});
if (!captions || !captions[i] || captions[i].active !== _active) {
set = true;
}
}
if (set) {
setCaptions(nextCaptions);
}
}
}
}
}, [captions, height, videoRef, width]);
var play = useCallback(function () {
return videoRef.current.play();
}, [videoRef]);
var pause = useCallback(function () {
return videoRef.current.pause();
}, [videoRef]);
var scrub = useCallback(function (event) {
if (scrubberRef.current) {
var scrubberRect = scrubberRef.current.getBoundingClientRect();
var percent = (event.clientX - scrubberRect.left) / scrubberRect.width;
setScrubTime(duration * percent);
}
}, [duration]);
var seek = useCallback(function (event) {
if (scrubberRef.current) {
var scrubberRect = scrubberRef.current.getBoundingClientRect();
var percent = (event.clientX - scrubberRect.left) / scrubberRect.width;
if (duration) videoRef.current.currentTime = duration * percent;
}
}, [duration, videoRef]);
var louder = useCallback(function () {
videoRef.current.volume += VOLUME_STEP;
}, [videoRef]);
var quieter = useCallback(function () {
videoRef.current.volume -= VOLUME_STEP;
}, [videoRef]);
var showCaptions = function showCaptions(index) {
var textTracks = videoRef.current.textTracks;
for (var i = 0; i < textTracks.length; i += 1) {
textTracks[i].mode = i === index ? 'showing' : 'hidden';
}
};
var fullscreen = useCallback(function () {
var video = videoRef.current;
if (video.requestFullscreen) {
video.requestFullscreen();
} else if (video.msRequestFullscreen) {
video.msRequestFullscreen();
} else if (video.mozRequestFullScreen) {
video.mozRequestFullScreen();
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen();
} else {
console.warn("This browser doesn't support fullscreen.");
}
}, [videoRef]);
var controlsElement;
if (controls) {
var over = controls === 'over';
var background = over ? theme.video.controls && theme.video.controls.background || {
color: 'background-back',
opacity: 'strong',
dark: true
} : undefined;
var iconColor = over && (theme.video.icons.color || 'text');
var formattedTime = formatTime(scrubTime || currentTime || duration);
var Icons = {
ClosedCaption: theme.video.icons.closedCaption,
Configure: theme.video.icons.configure,
FullScreen: theme.video.icons.fullScreen,
Pause: theme.video.icons.pause,
Play: theme.video.icons.play,
ReduceVolume: theme.video.icons.reduceVolume,
Volume: theme.video.icons.volume
};
var captionControls = captions.map(function (caption) {
return {
icon: caption.label ? undefined : /*#__PURE__*/React.createElement(Icons.ClosedCaption, {
color: iconColor
}),
label: caption.label,
active: caption.active,
onClick: function onClick() {
return showCaptions(caption.active ? -1 : 0);
}
};
});
controlsElement = /*#__PURE__*/React.createElement(StyledVideoControls, {
over: over,
active: !hasPlayed || controls === 'below' || over && interacting,
onBlur: function onBlur() {
if (!containsFocus(containerRef.current)) setInteracting(false);
}
}, /*#__PURE__*/React.createElement(Box, {
direction: "row",
align: "center",
justify: "between",
background: background
}, /*#__PURE__*/React.createElement(Button, {
icon: playing ? /*#__PURE__*/React.createElement(Icons.Pause, {
color: iconColor,
a11yTitle: messages.pauseButton
}) : /*#__PURE__*/React.createElement(Icons.Play, {
color: iconColor,
a11yTitle: messages.playButton
}),
hoverIndicator: "background",
onClick: playing ? pause : play
}), /*#__PURE__*/React.createElement(Box, {
direction: "row",
align: "center",
flex: true
}, /*#__PURE__*/React.createElement(Box, {
flex: true
}, /*#__PURE__*/React.createElement(Stack, null, /*#__PURE__*/React.createElement(Meter, {
"aria-label": messages.progressMeter,
background: over ? theme.video.scrubber && theme.video.scrubber.track && theme.video.scrubber.track.color || 'dark-3' : undefined,
size: "full",
thickness: "small",
values: [{
value: percentagePlayed || 0
}]
}), /*#__PURE__*/React.createElement(StyledVideoScrubber, {
"aria-label": messages.scrubber,
ref: scrubberRef,
tabIndex: 0,
role: "button",
value: scrubTime ? Math.round(scrubTime / duration * 100) : undefined,
onMouseMove: scrub,
onMouseLeave: function onMouseLeave() {
return setScrubTime(undefined);
},
onClick: seek
}))), /*#__PURE__*/React.createElement(Box, {
pad: {
horizontal: 'small'
}
}, /*#__PURE__*/React.createElement(Text, {
margin: "none"
}, formattedTime))), /*#__PURE__*/React.createElement(Menu, {
icon: /*#__PURE__*/React.createElement(Icons.Configure, {
color: iconColor
}),
dropAlign: {
bottom: 'top',
right: 'right'
},
dropBackground: background,
messages: {
openMenu: messages.openMenu,
closeMenu: messages.closeMenu
},
items: [{
icon: /*#__PURE__*/React.createElement(Icons.Volume, {
color: iconColor,
a11yTitle: messages.volumeUp
}),
onClick: volume <= 1 - VOLUME_STEP ? louder : undefined,
close: false
}, {
icon: /*#__PURE__*/React.createElement(Icons.ReduceVolume, {
color: iconColor,
a11yTitle: messages.volumeDown
}),
onClick: volume >= VOLUME_STEP ? quieter : undefined,
close: false
}].concat(captionControls, [{
icon: /*#__PURE__*/React.createElement(Icons.FullScreen, {
color: iconColor,
a11yTitle: messages.fullScreen
}),
onClick: fullscreen
}])
})));
}
var mouseEventListeners;
if (controls === 'over') {
mouseEventListeners = {
onMouseEnter: function onMouseEnter() {
return setInteracting(true);
},
onMouseMove: function onMouseMove() {
return setInteracting(true);
},
onTouchStart: function onTouchStart() {
return setInteracting(true);
}
};
}
var style;
if (rest.fit === 'contain' && controls === 'over') {
// constrain the size to fit the aspect ratio so the controls
// overlap correctly
if (width) {
style = {
width: width
};
} else if (height) {
style = {
height: height
};
}
}
return /*#__PURE__*/React.createElement(StyledVideoContainer, _extends({
ref: containerRef
}, mouseEventListeners, {
alignSelf: alignSelf,
gridArea: gridArea,
margin: margin,
style: style
}), /*#__PURE__*/React.createElement(StyledVideo, _extends({}, rest, {
ref: videoRef,
onDurationChange: function onDurationChange(event) {
var video = videoRef.current;
setDuration(video.duration);
setPercentagePlayed(video.currentTime / video.duration * 100);
if (_onDurationChange) _onDurationChange(event);
},
onEnded: function onEnded(event) {
setPlaying(false);
if (_onEnded) _onEnded(event);
},
onPause: function onPause(event) {
setPlaying(false);
if (_onPause) _onPause(event);
},
onPlay: function onPlay(event) {
setPlaying(true);
setHasPlayed(true);
if (_onPlay) _onPlay(event);
},
onTimeUpdate: function onTimeUpdate(event) {
var video = videoRef.current;
setCurrentTime(video.currentTime);
setPercentagePlayed(video.currentTime / video.duration * 100);
if (_onTimeUpdate) _onTimeUpdate(event);
},
onVolumeChange: function onVolumeChange(event) {
setVolume(videoRef.current.volume);
if (_onVolumeChange) _onVolumeChange(event);
},
autoPlay: autoPlay || false,
loop: loop || false
}), children), controlsElement);
});
Video.defaultProps = {
messages: {
closeMenu: 'close menu',
fullScreen: 'full screen',
progressMeter: 'video progress',
scrubber: 'scrubber',
openMenu: 'open menu',
pauseButton: 'pause',
playButton: 'play',
volumeDown: 'volume down',
volumeUp: 'volume up'
}
};
Video.displayName = 'Video';
var VideoDoc;
if (process.env.NODE_ENV !== 'production') {
// eslint-disable-next-line global-require
VideoDoc = require('./doc').doc(Video);
}
var VideoWrapper = VideoDoc || Video;
export { VideoWrapper as Video };