grommet
Version:
focus on the essential experience
590 lines (582 loc) • 22.6 kB
JavaScript
"use strict";
exports.__esModule = true;
exports.Video = void 0;
var _react = _interopRequireWildcard(require("react"));
var _useIsomorphicLayoutEffect = require("../../utils/use-isomorphic-layout-effect");
var _AnnounceContext = require("../../contexts/AnnounceContext");
var _Box = require("../Box");
var _Button = require("../Button");
var _Menu = require("../Menu");
var _Meter = require("../Meter");
var _Stack = require("../Stack");
var _Text = require("../Text");
var _Keyboard = require("../Keyboard");
var _utils = require("../../utils");
var _StyledVideo = require("./StyledVideo");
var _MessageContext = require("../../contexts/MessageContext");
var _propTypes = require("./propTypes");
var _useThemeValue2 = require("../../utils/useThemeValue");
var _excluded = ["alignSelf", "autoPlay", "children", "controls", "gridArea", "loop", "margin", "messages", "mute", "onDurationChange", "onEnded", "onPause", "onPlay", "onTimeUpdate", "onVolumeChange", "skipInterval"];
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
// Split the volume control into 6 segments. Empirically determined.
var VOLUME_STEP = 0.166667;
var formatTime = function formatTime(time) {
var minutes = Math.trunc(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 = exports.Video = /*#__PURE__*/(0, _react.forwardRef)(function (_ref, ref) {
var alignSelf = _ref.alignSelf,
autoPlay = _ref.autoPlay,
children = _ref.children,
controlsProp = _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,
skipInterval = _ref.skipInterval,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
var _useThemeValue = (0, _useThemeValue2.useThemeValue)(),
theme = _useThemeValue.theme,
passThemeFlag = _useThemeValue.passThemeFlag;
var _useContext = (0, _react.useContext)(_MessageContext.MessageContext),
format = _useContext.format;
var announce = (0, _react.useContext)(_AnnounceContext.AnnounceContext);
var _useState = (0, _react.useState)([]),
captions = _useState[0],
setCaptions = _useState[1];
var _useState2 = (0, _react.useState)(),
currentTime = _useState2[0],
setCurrentTime = _useState2[1];
var _useState3 = (0, _react.useState)(),
duration = _useState3[0],
setDuration = _useState3[1];
var _useState4 = (0, _react.useState)(),
percentagePlayed = _useState4[0],
setPercentagePlayed = _useState4[1];
var _useState5 = (0, _react.useState)(false),
playing = _useState5[0],
setPlaying = _useState5[1];
var _useState6 = (0, _react.useState)(false),
announceAudioDescription = _useState6[0],
setAnnounceAudioDescription = _useState6[1];
var _useState7 = (0, _react.useState)(),
scrubTime = _useState7[0],
setScrubTime = _useState7[1];
var _useState8 = (0, _react.useState)(),
volume = _useState8[0],
setVolume = _useState8[1];
var _useState9 = (0, _react.useState)(false),
hasPlayed = _useState9[0],
setHasPlayed = _useState9[1];
var _useState0 = (0, _react.useState)(),
interacting = _useState0[0],
setInteracting = _useState0[1];
var _useState1 = (0, _react.useState)(),
height = _useState1[0],
setHeight = _useState1[1];
var _useState10 = (0, _react.useState)(),
width = _useState10[0],
setWidth = _useState10[1];
var containerRef = (0, _react.useRef)();
var scrubberRef = (0, _react.useRef)();
var videoRef = (0, _utils.useForwardedRef)(ref);
var controls = (0, _react.useMemo)(function () {
var result;
if (typeof controlsProp === 'string' || typeof controlsProp === 'boolean') {
result = {
items: ['volume', 'fullScreen'],
position: controlsProp
};
} else {
result = {
items: (controlsProp == null ? void 0 : controlsProp.items) || ['volume', 'fullScreen'],
position: (controlsProp == null ? void 0 : controlsProp.position) || 'over'
};
}
return result;
}, [controlsProp]);
// mute if needed
(0, _react.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
(0, _react.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
(0, _react.useEffect)(function () {
var timer = setTimeout(function () {
if (interacting && !(0, _utils.containsFocus)(containerRef.current)) {
setInteracting(false);
}
}, 3000);
return function () {
return clearTimeout(timer);
};
}, [interacting]);
// track which audio description track is active
var _useState11 = (0, _react.useState)(),
activeTrack = _useState11[0],
setActiveTrack = _useState11[1];
(0, _useIsomorphicLayoutEffect.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;
var nextCaptions = [];
var set = false;
// iterate through all of the tracks provided
var _loop = function _loop() {
var track = textTracks[i];
var active = track.mode === 'showing';
var getActiveTrack = function getActiveTrack(currentVideoTime) {
var nextActiveTrack;
for (var j = 0; j < track.cues.length; j += 1) {
var _track$cues$j, _track$cues$j2;
if (currentVideoTime > (track == null || (_track$cues$j = track.cues[j]) == null ? void 0 : _track$cues$j.startTime) && currentVideoTime < (track == null || (_track$cues$j2 = track.cues[j]) == null ? void 0 : _track$cues$j2.endTime)) {
var _track$cues$j3;
nextActiveTrack = track == null || (_track$cues$j3 = track.cues[j]) == null ? void 0 : _track$cues$j3.text;
}
}
return nextActiveTrack;
};
// track is an audio description
if (track.kind === 'descriptions') {
if (announceAudioDescription) {
video.ontimeupdate = function () {
var nextActiveTrack = getActiveTrack(video.currentTime);
if (activeTrack !== nextActiveTrack) {
if (nextActiveTrack) {
announce(nextActiveTrack, 'assertive');
}
setActiveTrack(nextActiveTrack);
}
};
}
}
// otherwise treat as captions
else {
nextCaptions.push({
label: track.label,
active: active
});
if (!captions || !captions[i] || captions[i].active !== active) {
set = true;
}
if (set) {
setCaptions(nextCaptions);
}
}
};
for (var i = 0; i < textTracks.length; i += 1) {
_loop();
}
}
}, [activeTrack, announce, announceAudioDescription, captions, height, videoRef, width]);
var play = (0, _react.useCallback)(function () {
return videoRef.current.play();
}, [videoRef]);
var pause = (0, _react.useCallback)(function () {
return videoRef.current.pause();
}, [videoRef]);
var scrub = (0, _react.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 = (0, _react.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 seekForward = (0, _react.useCallback)(function () {
setInteracting(true);
videoRef.current.currentTime += skipInterval || theme.video.scrubber.interval;
}, [skipInterval, theme.video.scrubber.interval, videoRef]);
var seekBackward = (0, _react.useCallback)(function () {
setInteracting(true);
videoRef.current.currentTime -= skipInterval || theme.video.scrubber.interval;
}, [skipInterval, theme.video.scrubber.interval, videoRef]);
var louder = (0, _react.useCallback)(function () {
videoRef.current.volume += VOLUME_STEP;
}, [videoRef]);
var quieter = (0, _react.useCallback)(function () {
videoRef.current.volume -= VOLUME_STEP;
}, [videoRef]);
var showCaptions = (0, _react.useCallback)(function (index) {
var textTracks = videoRef.current.textTracks;
for (var i = 0; i < textTracks.length; i += 1) textTracks[i].mode = i === index ? 'showing' : 'hidden';
}, [videoRef]);
var fullscreen = (0, _react.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 != null && controls.position) {
var _controls$items, _theme$video, _theme$video2;
var over = controls.position === '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,
Description: theme.video.icons.description
};
var captionControls = captions.map(function (caption, index) {
return {
icon: caption.label ? undefined : /*#__PURE__*/_react["default"].createElement(Icons.ClosedCaption, {
color: iconColor
}),
label: caption.label,
active: caption.active,
a11yTitle: caption.label || format({
id: 'video.captions',
messages: messages
}),
onClick: function onClick() {
showCaptions(caption.active ? -1 : index);
var updatedCaptions = [];
for (var i = 0; i < captions.length; i += 1) {
updatedCaptions.push(captions[i]);
// set other captions to active=false
if (i !== index && updatedCaptions[i].active) updatedCaptions[i].active = false;
// set the currently selected captions to active
else if (i === index) updatedCaptions[i].active = !captions[index].active;
}
setCaptions(updatedCaptions);
}
};
});
var descriptionControls = {
icon: /*#__PURE__*/_react["default"].createElement(Icons.Description, {
color: iconColor
}),
a11yTitle: format({
id: 'video.audioDescriptions',
messages: messages
}),
active: announceAudioDescription,
onClick: function onClick() {
return setAnnounceAudioDescription(!announceAudioDescription);
}
};
var volumeControls = ['volume', 'reduceVolume'].map(function (control) {
return {
icon: control === 'volume' ? /*#__PURE__*/_react["default"].createElement(Icons.Volume, {
color: iconColor
}) : /*#__PURE__*/_react["default"].createElement(Icons.ReduceVolume, {
color: iconColor
}),
a11yTitle: format({
id: control === 'volume' ? 'video.volumeUp' : 'video.volumeDown',
messages: messages
}),
onClick: function onClick() {
if (volume <= 1 - VOLUME_STEP && control === 'volume') {
return louder();
}
if (volume >= VOLUME_STEP && control === 'reduceVolume') {
return quieter();
}
return undefined;
},
close: false
};
});
var buttonProps = {
captions: captionControls,
descriptions: descriptionControls,
volume: volumeControls,
fullScreen: {
icon: /*#__PURE__*/_react["default"].createElement(Icons.FullScreen, {
color: iconColor
}),
a11yTitle: format({
id: 'video.fullScreen',
messages: messages
}),
onClick: fullscreen
},
pause: {
icon: /*#__PURE__*/_react["default"].createElement(Icons.Pause, {
color: iconColor
}),
a11yTitle: format({
id: 'video.pauseButton',
messages: messages
}),
disabled: !playing,
onClick: pause
},
play: {
icon: /*#__PURE__*/_react["default"].createElement(Icons.Play, {
color: iconColor
}),
a11yTitle: format({
id: 'video.playButton',
messages: messages
}),
disabled: playing,
onClick: play
}
};
var controlsMenuItems = [];
(_controls$items = controls.items) == null || _controls$items.map(function (item) {
if (item === 'volume') {
volumeControls.map(function (control) {
return controlsMenuItems.push(control);
});
return undefined;
}
if (item === 'captions' && typeof buttonProps[item] === 'object') {
for (var i = 0; i < buttonProps[item].length; i += 1) controlsMenuItems.push(buttonProps[item][i]);
return undefined;
}
if (item === 'descriptions') {
controlsMenuItems.push(buttonProps[item]);
return undefined;
}
if (typeof item === 'string') {
return controlsMenuItems.push(buttonProps[item]);
}
return controlsMenuItems.push(item);
});
controlsElement = /*#__PURE__*/_react["default"].createElement(_StyledVideo.StyledVideoControls, {
over: over,
active: !hasPlayed || controls.position === 'below' || over && interacting,
onBlur: function onBlur() {
if (!(0, _utils.containsFocus)(containerRef.current)) setInteracting(false);
}
}, /*#__PURE__*/_react["default"].createElement(_Box.Box, {
direction: "row",
align: "center",
justify: "between",
background: background
}, /*#__PURE__*/_react["default"].createElement(_Button.Button, {
icon: playing ? /*#__PURE__*/_react["default"].createElement(Icons.Pause, {
color: iconColor,
a11yTitle: format({
id: 'video.pauseButton',
messages: messages
})
}) : /*#__PURE__*/_react["default"].createElement(Icons.Play, {
color: iconColor,
a11yTitle: format({
id: 'video.playButton',
messages: messages
})
}),
hoverIndicator: "background",
onClick: playing ? pause : play,
onFocus: function onFocus() {
return setInteracting(true);
}
}), /*#__PURE__*/_react["default"].createElement(_Box.Box, {
direction: "row",
align: "center",
flex: true
}, /*#__PURE__*/_react["default"].createElement(_Box.Box, {
flex: true
}, /*#__PURE__*/_react["default"].createElement(_Stack.Stack, null, /*#__PURE__*/_react["default"].createElement(_Meter.Meter, {
"aria-label": format({
id: 'video.progressMeter',
messages: messages
}),
background: over ? theme.video.scrubber && theme.video.scrubber.track && theme.video.scrubber.track.color || 'dark-3' : undefined,
size: "full",
thickness: (_theme$video = theme.video) == null || (_theme$video = _theme$video.scrubber) == null ? void 0 : _theme$video.thickness,
values: [{
value: percentagePlayed || 0
}]
}), /*#__PURE__*/_react["default"].createElement(_StyledVideo.StyledVideoScrubber, _extends({
"aria-label": format({
id: 'video.scrubber',
messages: messages
}),
ref: scrubberRef,
tabIndex: 0,
role: "button",
value: scrubTime ? Math.round(scrubTime / duration * 100) : undefined,
onMouseMove: scrub,
onMouseLeave: function onMouseLeave() {
return setScrubTime(undefined);
},
onClick: seek,
onFocus: function onFocus() {
return setInteracting(true);
}
}, passThemeFlag)))), /*#__PURE__*/_react["default"].createElement(_Box.Box, {
pad: theme == null || (_theme$video2 = theme.video) == null || (_theme$video2 = _theme$video2.time) == null || (_theme$video2 = _theme$video2.container) == null ? void 0 : _theme$video2.pad
}, /*#__PURE__*/_react["default"].createElement(_Text.Text, {
margin: "none"
}, formattedTime))), /*#__PURE__*/_react["default"].createElement(_Menu.Menu, {
icon: /*#__PURE__*/_react["default"].createElement(Icons.Configure, {
color: iconColor
}),
dropAlign: {
bottom: 'top',
right: 'right'
},
dropBackground: background,
messages: {
openMenu: format({
id: 'video.openMenu',
messages: messages
}),
closeMenu: format({
id: 'video.closeMenu',
messages: messages
})
},
items: [].concat(controlsMenuItems),
onFocus: function onFocus() {
return setInteracting(true);
}
})));
}
var mouseEventListeners;
if ((controls == null ? void 0 : controls.position) === '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 == null ? void 0 : controls.position) === '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["default"].createElement(_Keyboard.Keyboard, {
onLeft: seekBackward,
onRight: seekForward
}, /*#__PURE__*/_react["default"].createElement(_StyledVideo.StyledVideoContainer, _extends({
ref: containerRef
}, mouseEventListeners, {
alignSelf: alignSelf,
gridArea: gridArea,
margin: margin,
style: style,
tabIndex: "-1"
}, passThemeFlag), /*#__PURE__*/_react["default"].createElement(_StyledVideo.StyledVideo, _extends({}, passThemeFlag, 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.displayName = 'Video';
Video.propTypes = _propTypes.VideoPropTypes;