xref: /expo/docs/components/plugins/Video.tsx (revision c9f6a058)
1import { css } from '@emotion/react';
2import { borderRadius, breakpoints } from '@expo/styleguide-base';
3import { PropsWithChildren, useState } from 'react';
4import ReactPlayer from 'react-player';
5import VisibilitySensor from 'react-visibility-sensor';
6
7const PLAYER_WIDTH = '100%' as const;
8const PLAYER_HEIGHT = 400 as const;
9const YOUTUBE_DOMAINS = ['youtube.com', 'youtu.be'] as const;
10
11type VideoProps = PropsWithChildren<{
12  controls?: any;
13  spaceAfter?: boolean | number;
14  url?: string;
15  file?: string;
16  loop?: boolean;
17}>;
18
19const Video = ({ controls, spaceAfter, url, file, loop = true }: VideoProps) => {
20  const [hover, setHover] = useState(false);
21  const [forceShowControls, setForceShowControls] = useState(isYouTubeDomain(url));
22
23  return (
24    <div
25      onClick={() => {
26        if (typeof controls === 'undefined' && !forceShowControls) {
27          setForceShowControls(true);
28        }
29      }}
30      style={hover ? { cursor: 'pointer' } : undefined}
31      onMouseEnter={() => setHover(true)}
32      onMouseLeave={() => setHover(false)}>
33      <VisibilitySensor partialVisibility>
34        {({ isVisible }: { isVisible: boolean }) => (
35          <div css={[videoWrapperStyle, { marginBottom: getInitialMarginBottom(spaceAfter) }]}>
36            <ReactPlayer
37              url={isVisible ? url || `/static/videos/${file}` : undefined}
38              className="react-player"
39              width={PLAYER_WIDTH}
40              height={PLAYER_HEIGHT}
41              style={playerStyle}
42              muted
43              playing={isVisible && !!file}
44              controls={typeof controls === 'undefined' ? forceShowControls : controls}
45              playsinline
46              loop={loop}
47            />
48            <div
49              css={[
50                videoWrapperStyle,
51                dimmerStyle,
52                {
53                  opacity: isVisible ? 0 : 0.7,
54                },
55              ]}
56            />
57          </div>
58        )}
59      </VisibilitySensor>
60    </div>
61  );
62};
63
64const getInitialMarginBottom = (spaceAfter: VideoProps['spaceAfter']) => {
65  if (typeof spaceAfter === 'undefined') {
66    return 30;
67  } else if (typeof spaceAfter === 'number') {
68    return spaceAfter;
69  } else if (spaceAfter) {
70    return 50;
71  }
72  return 0;
73};
74
75const isYouTubeDomain = (url?: string) => {
76  return url ? YOUTUBE_DOMAINS.some(domain => url.includes(domain)) : false;
77};
78
79const videoWrapperStyle = css({
80  position: 'relative',
81  width: PLAYER_WIDTH,
82  height: PLAYER_HEIGHT,
83  backgroundColor: '#000',
84});
85
86const playerStyle = css({
87  outline: 'none',
88  backgroundColor: '#000',
89  borderRadius: borderRadius.md,
90});
91
92const dimmerStyle = css({
93  pointerEvents: 'none',
94  position: 'absolute',
95  top: 0,
96  left: 0,
97  bottom: 0,
98  right: 0,
99  transition: 'opacity 0.5s ease-out',
100
101  [`@media screen and (max-width: ${breakpoints.medium + 124}px)`]: {
102    display: 'none',
103  },
104});
105
106export default Video;
107