React Three Fiber VideoTexture - Frequent "stalled" events when trying to load the video

I’m using React Three Fiber and THREE.VideoTexture to display videos as textures. However, some videos trigger frequent "stalled" events, especially in Safari, while not so much in Chrome.

I suspect this issue is related to how the video is being served or its encoding, but I’m struggling to pinpoint the root cause. When I open the video URL directly in the browser, it plays fine, which makes me think the issue lies somewhere between loading it in the WebGL context and the way it’s handled by Safari.

Code Snippet

Here’s how I’m setting basicaly loading the video

const videoTexture = useMemo(() => {
    if (!videoSrc) return null;

    // Create video element
    const video = document.createElement('video');
    video.src = videoSrc;
    video.crossOrigin = 'anonymous'; // Allow CORS if necessary
    video.loop = false;
    video.muted = true; // Required for autoplay in some browsers
    video.preload = 'auto';
    video.setAttribute('playsinline', '');
    video.setAttribute('webkit-playsinline', ''); // iOS-specific

    // Create a Three.js VideoTexture
    const texture = new THREE.VideoTexture(video);
    texture.minFilter = THREE.LinearFilter;
    texture.magFilter = THREE.LinearFilter;
    texture.format = THREE.RGBAFormat;

    return texture;
}, [videoSrc]);

useEffect(() => {
    if (!videoTexture) return;

    const handleCanPlay = () => {
        console.log('Video loaded successfully.', videoTexture.image.src);
    };

    const handleError = (e) => {
        console.error('Video encountered an error.', videoTexture.image.src, e);
        retryLoading();
    };

    const handleStalled = (e) => {
        console.warn('Video loading stalled.', videoTexture.image.src, e);
        retryLoading();
    };

    const retryLoading = () => {
        videoTexture.image.load();
    };

    // Event Listeners
    videoTexture.image.addEventListener('canplaythrough', handleCanPlay);
    videoTexture.image.addEventListener('error', handleError);
    videoTexture.image.addEventListener('stalled', handleStalled);
    videoTexture.image.load();

    // Cleanup
    return () => {
        videoTexture.image.preload = 'none';
        videoTexture.image.removeEventListener('canplaythrough', handleCanPlay);
        videoTexture.image.removeEventListener('error', handleError);
        videoTexture.image.removeEventListener('stalled', handleStalled);
    };
}, [videoTexture]);

And sometime I get Video loading stalled. (even for 5-6 times before I get Video loaded successfully.)

Video Encoding Details

Here’s the encoding info for a video that stalls often:

Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf61.7.100
  Duration: 00:00:33.60, start: 0.000000, bitrate: 814 kb/s
  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 960x720 [SAR 1:1 DAR 4:3], 812 kb/s, 30 fps, 30 tbr, 90k tbn (default)
      Metadata:
        handler_name    : VideoHandler

Server Configuration (Nginx)

I’m serving the video using Nginx, with the following configuration:

location ~* /assets/video/ {
    expires 30d;
    add_header Cache-Control "public";
    add_header Accept-Ranges bytes;

    # Proper MIME type for MP4 files
    types { video/mp4 mp4; }

    # Optimize file serving
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;

    # Enable byte-range requests for video seeking
    aio threads;
    output_buffers 1 64k;
    directio 512k;
}

Why does Safari trigger so many "stalled" events? Are there best practices for serving videos efficiently in a WebGL/React Three Fiber context? Could this be an issue with video encoding, MIME types, or how it's being served by Nginx? Any insights or suggestions would be greatly appreciated!

Keep in mind that I can not host the videos in CDN so of course the approch is not optimal but still it should be stable enough.

Upvotes: 0

Views: 26

Answers (0)

Related Questions