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