admin管理员组

文章数量:1391995

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>
  );
};

What I've Tried

  1. Audio Codec Compatibility

    • Converted all videos to use AAC audio codec (verified with FFprobe)
    • Using proper encoding parameters:
      • 44.1kHz sample rate
      • 2 channels (stereo)
      • 128k bitrate
  2. iOS-Specific Attributes @Apple Documentation

    • Added playsInline
    • Added webkit-playsinline
    • Added x-webkit-airplay="allow"
    • Removed custom play button to rely on native controls
    • Ensuring proper CORS headers
  3. Audio Unlocking Attempts

    if (isIOSDevice) {
      video.muted = true; // Start muted on iOS
      // Try to unmute on user interaction
      video.muted = false;
      video.play().catch(err => console.error('Error playing video:', err));
    }
    
  4. 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!

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>
  );
};

What I've Tried

  1. Audio Codec Compatibility

    • Converted all videos to use AAC audio codec (verified with FFprobe)
    • Using proper encoding parameters:
      • 44.1kHz sample rate
      • 2 channels (stereo)
      • 128k bitrate
  2. iOS-Specific Attributes @Apple Documentation

    • Added playsInline
    • Added webkit-playsinline
    • Added x-webkit-airplay="allow"
    • Removed custom play button to rely on native controls
    • Ensuring proper CORS headers
  3. Audio Unlocking Attempts

    if (isIOSDevice) {
      video.muted = true; // Start muted on iOS
      // Try to unmute on user interaction
      video.muted = false;
      video.play().catch(err => console.error('Error playing video:', err));
    }
    
  4. 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!

Share Improve this question edited Mar 14 at 17:44 galilei asked Mar 14 at 4:21 galileigalilei 852 silver badges6 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

You need to add muted attribute to video tag:

<video ref={videoRef} autoPlay={!isIOSDevice && autoplay} playsInline controls crossOrigin="anonymous" preload="auto"  muted=true >

本文标签: reactjsiOS Mobile Video Audio Playback Issues in ReactStack Overflow