When I click a play button on an iPhone in Safari, I get the following error:
NotAllowedError: The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission.
I searched on the internet and it seems that this issue has been around for a long time but there is no clear solution to it.
Here is my code. It works everywhere except iOS Safari.
import { Box } from '@chakra-ui/react';
import { useTranslation } from 'libs/i18next';
import React, { useEffect, useRef, useState } from 'react';
import { PauseIcon, PlayIcon } from 'theme/icons';
import { visuallyHiddenCss } from 'utils/style-utils';
import {
buttonHoverArea,
playButtonCss,
playIconStyles,
videoContainerCss,
videoCss,
} from './Video.styles';
export interface Video extends React.HTMLProps<HTMLVideoElement> {
src: string;
noVideoText?: string;
className?: string;
isEnabled?: boolean;
coversParent?: boolean;
handlePlayPress?: (videoPlaying: boolean) => void;
}
/**
* Default HTML5 video player with a play button and a preview thumbnail image.
*/
export const Video = ({
src,
noVideoText,
className,
loop = false,
autoPlay = false, // AutoPlay doesn't work currently
isEnabled = true,
coversParent = false,
handlePlayPress,
...props
}: Video): React.ReactElement => {
const [t] = useTranslation(['common']);
const videoRef = useRef<HTMLVideoElement>(null);
const buttonRef = useRef<HTMLButtonElement>(null);
// use videoStarted state to hide the play button once it has been started
const [videoPlaying, setVideoPlaying] = useState<boolean>(false);
const pauseVideo = () => {
setVideoPlaying(false);
};
const toggleVideo = () => {
if (handlePlayPress) {
handlePlayPress(!videoPlaying);
}
setVideoPlaying(!videoPlaying);
};
const defaultProps: Partial<Video> = {
width: '100%',
height: '100%',
preload: 'none',
onClick: pauseVideo,
tabIndex: -1,
};
const videoProps = Object.assign(defaultProps, props);
useEffect(() => {
if (videoRef.current && props.muted) {
// force muted prop
// https://github.com/facebook/react/issues/10389#issuecomment-605689475
videoRef.current.setAttribute('muted', '');
videoRef.current.defaultMuted = true;
}
}, [props.muted, videoRef]);
useEffect(() => {
if (isEnabled && videoPlaying) {
videoRef.current?.play();
buttonRef.current?.blur();
videoRef.current?.focus();
} else {
videoRef.current?.pause();
}
}, [isEnabled, videoPlaying, videoRef]);
const PlayPauseIcon = videoPlaying ? PauseIcon : PlayIcon;
return (
<Box
css={[
videoContainerCss.base,
coversParent ? videoContainerCss.cover : undefined,
]}
className={className}
>
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
<video
ref={videoRef}
css={videoCss}
{...videoProps}
autoPlay={autoPlay}
loop={loop}
>
<source src={src} type={'video/mp4'} />
{noVideoText}
</video>
{isEnabled && (
<div
css={[buttonHoverArea.base, videoPlaying && buttonHoverArea.playing]}
>
<button
type="button"
ref={buttonRef}
css={playButtonCss}
onClick={toggleVideo}
// prevent hidden elements from being focused
>
<span css={visuallyHiddenCss}>
{videoPlaying ? t('common:video.pause') : t('common:video.play')}
</span>
<PlayPauseIcon
css={!videoPlaying && playIconStyles}
boxSize={'6rem'}
aria-hidden
/>
</button>
</div>
)}
</Box>
);
};