diff --git a/CHANGELOG.md b/CHANGELOG.md
index 12521f3a3..33c4f4d4e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ hideFromSearchEngines: true
- Replace `id` with `aria-label` in `VimeoPlayImage` to prevent duplicated `id`s when more than one instance is on the same page. [#438](https://github.com/mapbox/dr-ui/pull/438)
- Make `filename` optional for `CodeSnippetTitle`.
- Use passive event listeners on `Search`, `NumberedCodeSnippet`, and `OnThisPage` to improve performance. [#437](https://github.com/mapbox/dr-ui/pull/437)
+- Make `Video` disable autoplay by default. [#276](https://github.com/mapbox/dr-ui/pull/276)
- Load the `Search` component with a facade to improve performance. [#430](https://github.com/mapbox/dr-ui/pull/430)
## 3.3.1
diff --git a/src/components/phone/__tests__/__snapshots__/phone.test.js.snap b/src/components/phone/__tests__/__snapshots__/phone.test.js.snap
index 3d1696e08..d826ed429 100644
--- a/src/components/phone/__tests__/__snapshots__/phone.test.js.snap
+++ b/src/components/phone/__tests__/__snapshots__/phone.test.js.snap
@@ -302,26 +302,52 @@ exports[`phone iOS landscape renders as expected 1`] = `
-
+
+
diff --git a/src/components/video/__tests__/__snapshots__/video.test.js.snap b/src/components/video/__tests__/__snapshots__/video.test.js.snap
index f91a9d784..0c835b26b 100644
--- a/src/components/video/__tests__/__snapshots__/video.test.js.snap
+++ b/src/components/video/__tests__/__snapshots__/video.test.js.snap
@@ -1,51 +1,207 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`video Basic renders as expected 1`] = `
-
+exports[`video Basic. Your system settings do not prefer reduced motion, the video will autoplay (unless your browser settings block autoplay). renders as expected 1`] = `
+
+`;
+
+exports[`video The video will not autoplay. Sets autoplay={false} and loop={false}. A preview image will not appear on iOS. renders as expected 1`] = `
+
+
+
+`;
+
+exports[`video The video will not autoplay. Uses \`poster\` to set preview image. renders as expected 1`] = `
+
+
+
diff --git a/src/components/video/__tests__/video-test-cases.js b/src/components/video/__tests__/video-test-cases.js
index 9a198ca6c..2496320d6 100644
--- a/src/components/video/__tests__/video-test-cases.js
+++ b/src/components/video/__tests__/video-test-cases.js
@@ -1,15 +1,38 @@
import React from 'react';
import Basic from '../examples/basic';
+import Settings from '../examples/settings';
+import Poster from '../examples/poster';
import Video from '../video';
const testCases = {};
const noRenderCases = {};
+const prefersReducedMotion =
+ typeof window !== 'undefined' && window.matchMedia !== undefined
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
+ : false;
+
testCases.basic = {
- description: 'Basic',
+ description: `Basic. Your system settings${
+ prefersReducedMotion ? ' ' : ' do not '
+ }prefer reduced motion, the video will${
+ prefersReducedMotion ? ' not ' : ' '
+ }autoplay (unless your browser settings block autoplay).`,
element:
};
+testCases.settings = {
+ description:
+ 'The video will not autoplay. Sets autoplay={false} and loop={false}. A preview image will not appear on iOS.',
+ element:
+};
+
+testCases.poster = {
+ description:
+ 'The video will not autoplay. Uses `poster` to set preview image.',
+ element:
+};
+
noRenderCases.reducedMotion = {
component: Video,
description: 'Reduced motion',
diff --git a/src/components/video/__tests__/video.test.js b/src/components/video/__tests__/video.test.js
index b1279e615..eff771fd1 100644
--- a/src/components/video/__tests__/video.test.js
+++ b/src/components/video/__tests__/video.test.js
@@ -29,6 +29,48 @@ describe('video', () => {
});
});
+ describe(testCases.settings.description, () => {
+ let testCase;
+ let wrapper;
+ let tree;
+
+ beforeEach(() => {
+ window.matchMedia = jest.fn().mockImplementation((query) => {
+ return {
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(),
+ removeListener: jest.fn()
+ };
+ });
+
+ testCase = testCases.settings;
+ wrapper = renderer.create(testCase.element);
+ tree = wrapper.toJSON();
+ });
+
+ test('renders as expected', () => {
+ expect(tree).toMatchSnapshot();
+ });
+ });
+
+ describe(testCases.poster.description, () => {
+ let testCase;
+ let wrapper;
+ let tree;
+
+ beforeEach(() => {
+ testCase = testCases.poster;
+ wrapper = renderer.create(testCase.element);
+ tree = wrapper.toJSON();
+ });
+
+ test('renders as expected', () => {
+ expect(tree).toMatchSnapshot();
+ });
+ });
+
describe(noRenderCases.reducedMotion.description, () => {
let testCase;
let wrapper;
diff --git a/src/components/video/examples/poster.js b/src/components/video/examples/poster.js
new file mode 100644
index 000000000..3cb02ae56
--- /dev/null
+++ b/src/components/video/examples/poster.js
@@ -0,0 +1,18 @@
+/*
+The video will not autoplay. Uses `poster` to set preview image.
+*/
+import React from 'react';
+import Video from '../video';
+
+export default class Example extends React.PureComponent {
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/video/examples/settings.js b/src/components/video/examples/settings.js
new file mode 100644
index 000000000..6f19259e0
--- /dev/null
+++ b/src/components/video/examples/settings.js
@@ -0,0 +1,18 @@
+/*
+Disable `autoplay` and `loop`.
+*/
+import React from 'react';
+import Video from '../video';
+
+export default class Example extends React.PureComponent {
+ render() {
+ return (
+
+ );
+ }
+}
diff --git a/src/components/video/video.js b/src/components/video/video.js
index fcb6fe1a1..ded143240 100644
--- a/src/components/video/video.js
+++ b/src/components/video/video.js
@@ -1,37 +1,108 @@
+/* eslint-disable jsx-a11y/media-has-caption */
import React from 'react';
import PropTypes from 'prop-types';
-
+import { VimeoPlayImage } from '../related-page/vimeo';
+/*
+ * The state defines the user's preferences for the video settings (autoplay and loop).
+ * The props for Video will only be taken into account if the user does not prefer reduced motion.
+ * In `componentDidMount`, Video will determine the user's settings.
+ * - If the user prefers reduced motion, then Video will use the `state` for the video settings.
+ * - If the user does not prefer reduced motion, then Video will use the props for the video settings.
+ */
export default class Video extends React.PureComponent {
- render() {
- let videoProps = {
- autoPlay: true,
- loop: true
+ constructor(props) {
+ super(props);
+ // By default, Video will assume that the user prefers reduced motion
+ this.state = {
+ isPlaying: false, // The video is not playing
+ autoPlay: undefined, // Turn off autoplay
+ loop: undefined // Turn off loop
};
- const reducedMotion =
+ this.video = React.createRef();
+ }
+
+ componentDidMount() {
+ // check the users preference
+ const prefersReducedMotion =
typeof window !== 'undefined'
? window.matchMedia('(prefers-reduced-motion: reduce)').matches
: false;
- if (reducedMotion) {
- videoProps = {
- autoPlay: undefined,
- loop: undefined,
- controls: true
- };
+ // if `prefers-reduced-motion: reduce` is not set, use props to override state defaults
+ if (!prefersReducedMotion) {
+ this.setState({
+ autoPlay: this.props.autoplay,
+ loop: this.props.loop
+ });
}
+ }
+
+ // show video controls on hover
+ onHover = () => {
+ this.video.current.controls = true;
+ };
+
+ // hide video controls when user leaves the video container
+ onOut = () => {
+ this.video.current.controls = false;
+ };
+
+ // check if video is playing (helps us detect if user has autoplay enabled or disabled)
+ onPlay = () => {
+ this.setState({ isPlaying: true });
+ };
+
+ // check when video stops
+ onStop = () => {
+ this.setState({ isPlaying: false });
+ };
+
+ // play the video
+ playVideo = () => {
+ this.video.current.play();
+ };
+
+ render() {
+ const { src, title, muted, playsInline, poster } = this.props;
+ const { isPlaying, autoPlay, loop } = this.state;
+
+ const videoProps = {
+ autoPlay,
+ loop,
+ muted,
+ playsInline,
+ poster
+ };
+
return (
-
+
+ {!isPlaying && (
+
+ )}
@@ -39,7 +110,28 @@ export default class Video extends React.PureComponent {
}
}
+// Video will use props if the user does not prefer reduced motion, otherwise it will default to the values in the state. The component will determine if the user prefers reduced motion in componentDidMount and either use the state or props to apply the Video settings.
+Video.defaultProps = {
+ autoplay: true,
+ loop: true,
+ muted: true,
+ playsInline: true,
+ poster: undefined
+};
+
Video.propTypes = {
+ /** The path to the video. */
src: PropTypes.string.isRequired,
- title: PropTypes.string.isRequired
+ /** The title of the video. */
+ title: PropTypes.string.isRequired,
+ /** If true, the video will autoplay unless the user prefers reduced motion.*/
+ autoplay: PropTypes.bool,
+ /** If true, the video will loop unless the user prefers reduced motion. */
+ loop: PropTypes.bool,
+ /** If true, the video will be muted. */
+ muted: PropTypes.bool,
+ /** If true, the video will play on the page for iOS/Safari. If false, the video open fullscreen on play. */
+ playsInline: PropTypes.bool,
+ /** A path to a preview image for the video. */
+ poster: PropTypes.string
};
diff --git a/src/test-cases-app/files/browser-example-mp4-poster.png b/src/test-cases-app/files/browser-example-mp4-poster.png
new file mode 100644
index 000000000..264b7cf14
Binary files /dev/null and b/src/test-cases-app/files/browser-example-mp4-poster.png differ