Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

iOS Mobile Video Audio Playback Issues in React

I'm experiencing issues with audio playback in my React video player component specifically on iOS mobile devices (iPhone/iPad). Even after implementing several recommended solutions, including Apple's own guidelines, the audio still isn't working properly on iOS Safari. It works completely fine on Android. On iOS, I ensured the video doesn't autoplay (it requires user interaction). Here are all the details:

Environment

  • iOS Safari (latest version)
  • React 18
  • TypeScript
  • Video files: MP4 with AAC audio codec

Current Implementation

const VideoPlayer: React.FC<VideoPlayerProps> = ({
  src,
  autoplay = true,
}) => {
  const videoRef = useRef<HTMLVideoElement>(null);
  const isIOSDevice = isIOS(); // Custom iOS detection
  const [touchStartY, setTouchStartY] = useState<number | null>(null);
  const [touchStartTime, setTouchStartTime] = useState<number | null>(null);

  // Handle touch start event for gesture detection
  const handleTouchStart = (e: React.TouchEvent) => {
    setTouchStartY(e.touches[0].clientY);
    setTouchStartTime(Date.now());
  };

  // Handle touch end event with gesture validation
  const handleTouchEnd = (e: React.TouchEvent) => {
    if (touchStartY === null || touchStartTime === null) return;
    
    const touchEndY = e.changedTouches[0].clientY;
    const touchEndTime = Date.now();
    
    // Validate if it's a legitimate tap (not a scroll)
    const verticalDistance = Math.abs(touchEndY - touchStartY);
    const touchDuration = touchEndTime - touchStartTime;
    
    // Only trigger for quick taps (< 200ms) with minimal vertical movement
    if (touchDuration < 200 && verticalDistance < 10) {
      handleVideoInteraction(e);
    }
    
    setTouchStartY(null);
    setTouchStartTime(null);
  };

  // Simplified video interaction handler following Apple's guidelines
  const handleVideoInteraction = (e: React.MouseEvent | React.TouchEvent) => {
    console.log('Video interaction detected:', {
      type: e.type,
      timestamp: new Date().toISOString()
    });

    // Ensure keyboard is dismissed (iOS requirement)
    if (document.activeElement instanceof HTMLElement) {
      document.activeElement.blur();
    }
    
    e.stopPropagation();
    
    const video = videoRef.current;
    if (!video || !video.paused) return;
    
    // Attempt playback in response to user gesture
    video.play().catch(err => console.error('Error playing video:', err));
  };

  // Effect to handle video source and initial state
  useEffect(() => {
    console.log('VideoPlayer props:', { src, loadingState });
    
    setError(null);
    setLoadingState('initial');
    setShowPlayButton(false); // Never show custom play button on iOS
    
    if (videoRef.current) {
      // Set crossOrigin attribute for CORS
      videoRef.current.crossOrigin = "anonymous";
      
      if (autoplay && !hasPlayed && !isIOSDevice) {
        // Only autoplay on non-iOS devices
        dismissKeyboard();
        setHasPlayed(true);
      }
    }
  }, [src, autoplay, hasPlayed, isIOSDevice]);

  return (
    <Paper
      shadow="sm"
      radius="md"
      withBorder
      onClick={handleVideoInteraction}
      onTouchStart={handleTouchStart}
      onTouchEnd={handleTouchEnd}
    >
      <video
        ref={videoRef}
        autoPlay={!isIOSDevice && autoplay}
        playsInline
        controls
        crossOrigin="anonymous"
        preload="auto"
        onLoadedData={handleLoadedData}
        onLoadedMetadata={handleMetadataLoaded}
        onEnded={handleVideoEnd}
        onError={handleError}
        onPlay={dismissKeyboard}
        onClick={handleVideoInteraction}
        onTouchStart={handleTouchStart}
        onTouchEnd={handleTouchEnd}
        {...(!isFirefoxBrowser && { 
          "x-webkit-airplay": "allow", 
          "x-webkit-playsinline": true, 
          "webkit-playsinline": true 
        })}
      >
        <source src={videoSrc} type="video/mp4" />
      </video>
    </Paper>
  );
};
  1. Apple's Guidelines Implementation
    • Removed custom play controls on iOS
    • Using native video controls for user interaction
    • Ensuring audio playback is triggered by user gesture
    • Following Apple's audio session guidelines
    • Properly handling the canplaythrough event

Current Behavior

  • Video plays but without sound on iOS mobile
  • Mute/unmute button in native video controls doesn't work
  • Audio works fine on desktop browsers and Android devices
  • Videos are confirmed to have AAC audio codec
  • No console errors related to audio playback
  • User interaction doesn't trigger audio as expected

Questions

  1. Are there any additional iOS-specific requirements I'm missing?
  2. Could this be related to iOS audio session handling?
  3. Are there known issues with React's handling of video elements on iOS?
  4. Should I be implementing additional audio context initialization?

Any insights or suggestions would be greatly appreciated!

iOS Mobile Video Audio Playback Issues in React
 
 
Q