Skip to main content

Event Handling

React Native Video provides comprehensive event callbacks to monitor and respond to video playback states, errors, and user interactions. This guide covers all available events and how to use them effectively.

Event Types

Events are divided into two categories:
  • Player Events: Fired by the VideoPlayer instance
  • View Events: Fired by the VideoView component

Adding Event Listeners

There are two ways to add event listeners:

1. Using the Setup Function

Pass a setup function to useVideoPlayer:
import { useVideoPlayer } from 'react-native-video';

const player = useVideoPlayer('https://example.com/video.mp4', (player) => {
  player.addEventListener('onLoad', (data) => {
    console.log('Video loaded:', data.duration, 'seconds');
  });

  player.addEventListener('onError', (error) => {
    console.error('Playback error:', error.message);
  });
});

2. Using addEventListener Directly

import { useVideoPlayer } from 'react-native-video';
import { useEffect } from 'react';

const player = useVideoPlayer('https://example.com/video.mp4');

useEffect(() => {
  const subscription = player.addEventListener('onLoad', (data) => {
    console.log('Video loaded:', data.duration);
  });

  return () => {
    subscription.remove();
  };
}, []);
Always clean up event listeners by calling subscription.remove() or using the cleanup function in useEffect.

Player Events

Lifecycle Events

onLoadStart

Fired when the video starts loading.
player.addEventListener('onLoadStart', (data) => {
  console.log('Source type:', data.sourceType); // 'local' or 'network'
  console.log('Source:', data.source);
});
Payload:
  • sourceType: 'local' | 'network'
  • source: VideoPlayerSource object

onLoad

Fired when the video metadata is loaded and ready to play.
player.addEventListener('onLoad', (data) => {
  console.log('Duration:', data.duration);
  console.log('Dimensions:', data.width, 'x', data.height);
  console.log('Orientation:', data.orientation);
  console.log('Current time:', data.currentTime);
});
Payload:
  • currentTime: number - Current time in seconds
  • duration: number - Total duration in seconds (NaN if unknown)
  • width: number - Video width in pixels
  • height: number - Video height in pixels
  • orientation: VideoOrientation - Video orientation

onReadyToDisplay

Fired when the video is ready to be displayed on screen.
player.addEventListener('onReadyToDisplay', () => {
  console.log('Video ready to display');
});

Playback Events

onPlaybackStateChange

Fired when playback state changes.
player.addEventListener('onPlaybackStateChange', (data) => {
  console.log('Is playing:', data.isPlaying);
  console.log('Is buffering:', data.isBuffering);
});
Payload:
  • isPlaying: boolean
  • isBuffering: boolean

onStatusChange

Fired when the player status changes.
player.addEventListener('onStatusChange', (status) => {
  console.log('Status:', status); // 'playing', 'paused', 'buffering', etc.
});
Payload:
  • status: VideoPlayerStatus

onProgress

Fired periodically during playback to report progress.
player.addEventListener('onProgress', (data) => {
  console.log('Current time:', data.currentTime);
  console.log('Buffer duration:', data.bufferDuration);
});
Payload:
  • currentTime: number - Current playback time in seconds
  • bufferDuration: number - Time that player can play with buffer

onSeek

Fired when a seek operation completes.
player.addEventListener('onSeek', (seekTime) => {
  console.log('Seeked to:', seekTime, 'seconds');
});
Payload:
  • seekTime: number - Time seeked to in seconds

onEnd

Fired when video playback reaches the end.
player.addEventListener('onEnd', () => {
  console.log('Video finished');
});

onPlaybackRateChange

Fired when the playback rate changes.
player.addEventListener('onPlaybackRateChange', (rate) => {
  console.log('Playback rate:', rate); // 0.5, 1.0, 2.0, etc.
});
Payload:
  • rate: number - Current playback rate

Buffer Events

onBuffer

Fired when buffering state changes.
player.addEventListener('onBuffer', (buffering) => {
  if (buffering) {
    console.log('Buffering...');
  } else {
    console.log('Buffering complete');
  }
});
Payload:
  • buffering: boolean

Bandwidth Events

onBandwidthUpdate

Fired when the video bitrate changes (adaptive streaming).
player.addEventListener('onBandwidthUpdate', (data) => {
  console.log('Bitrate:', data.bitrate, 'bps');
  console.log('Resolution:', data.width, 'x', data.height); // Android only
});
Payload:
  • bitrate: number - Current bitrate in bits per second
  • width?: number - Video width (Android only)
  • height?: number - Video height (Android only)

Volume Events

onVolumeChange

Fired when volume or mute state changes.
player.addEventListener('onVolumeChange', (data) => {
  console.log('Volume:', data.volume); // 0.0 to 1.0
  console.log('Muted:', data.muted);
});
Payload:
  • volume: number - Volume level (0.0 = 0%, 1.0 = 100%)
  • muted: boolean - Whether the player is muted

Text Track Events

onTrackChange

Fired when the selected subtitle track changes.
player.addEventListener('onTrackChange', (track) => {
  if (track) {
    console.log('Track changed to:', track.label);
  } else {
    console.log('Subtitles disabled');
  }
});
Payload:
  • track: TextTrack | null - Selected track or null

onTextTrackDataChanged

Fired when subtitle text is displayed.
player.addEventListener('onTextTrackDataChanged', (texts) => {
  console.log('Current subtitles:', texts);
});
Payload:
  • texts: string[] - Array of currently displayed subtitle strings

Metadata Events

onTimedMetadata

Fired when timed metadata is received from the stream.
player.addEventListener('onTimedMetadata', (metadata) => {
  metadata.metadata.forEach((item) => {
    console.log('Key:', item.identifier);
    console.log('Value:', item.value);
  });
});
Payload:
  • metadata: Array of { identifier: string, value: string }

Platform-Specific Events

onAudioBecomingNoisy (Android)

Fired when audio becomes noisy (e.g., headphones unplugged).
player.addEventListener('onAudioBecomingNoisy', () => {
  console.log('Audio becoming noisy - pausing');
  player.pause();
});

onAudioFocusChange (Android)

Fired when audio focus changes.
player.addEventListener('onAudioFocusChange', (hasAudioFocus) => {
  console.log('Audio focus:', hasAudioFocus);
});
Payload:
  • hasAudioFocus: boolean

onExternalPlaybackChange (iOS)

Fired when external playback state changes (e.g., AirPlay).
player.addEventListener('onExternalPlaybackChange', (externalPlaybackActive) => {
  console.log('External playback:', externalPlaybackActive);
});
Payload:
  • externalPlaybackActive: boolean

Error Events

onError

Fired when a playback error occurs.
player.addEventListener('onError', (error) => {
  console.error('Error code:', error.code);
  console.error('Error message:', error.message);
});
Payload:
  • error: VideoRuntimeError with code and message

View Events

View events are passed as props to the VideoView component:

onPictureInPictureChange

<VideoView
  player={player}
  onPictureInPictureChange={(isActive) => {
    console.log('PiP active:', isActive);
  }}
/>

onFullscreenChange

<VideoView
  player={player}
  onFullscreenChange={(isFullscreen) => {
    console.log('Fullscreen:', isFullscreen);
  }}
/>

willEnterFullscreen / willExitFullscreen

<VideoView
  player={player}
  willEnterFullscreen={() => {
    console.log('About to enter fullscreen');
  }}
  willExitFullscreen={() => {
    console.log('About to exit fullscreen');
  }}
/>

willEnterPictureInPicture / willExitPictureInPicture

<VideoView
  player={player}
  willEnterPictureInPicture={() => {
    console.log('About to enter PiP');
  }}
  willExitPictureInPicture={() => {
    console.log('About to exit PiP');
  }}
/>

onControlsVisibleChange

player.addEventListener('onControlsVisibleChange', (visible) => {
  console.log('Controls visible:', visible);
});
Payload:
  • visible: boolean

Complete Event Monitoring Example

import { VideoView, useVideoPlayer } from 'react-native-video';
import { useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';

export function EventMonitor() {
  const [events, setEvents] = useState<string[]>([]);

  const logEvent = (name: string, data?: any) => {
    const message = `${name}: ${data ? JSON.stringify(data) : ''}`;
    setEvents(prev => [message, ...prev].slice(0, 10)); // Keep last 10 events
  };

  const player = useVideoPlayer('https://www.w3schools.com/html/mov_bbb.mp4', (player) => {
    // Lifecycle
    player.addEventListener('onLoadStart', (data) => {
      logEvent('onLoadStart', data.sourceType);
    });
    player.addEventListener('onLoad', (data) => {
      logEvent('onLoad', { duration: data.duration });
    });
    player.addEventListener('onReadyToDisplay', () => {
      logEvent('onReadyToDisplay');
    });

    // Playback
    player.addEventListener('onPlaybackStateChange', (data) => {
      logEvent('onPlaybackStateChange', data);
    });
    player.addEventListener('onProgress', (data) => {
      logEvent('onProgress', { time: data.currentTime.toFixed(1) });
    });
    player.addEventListener('onSeek', (time) => {
      logEvent('onSeek', time);
    });
    player.addEventListener('onEnd', () => {
      logEvent('onEnd');
    });

    // Buffer
    player.addEventListener('onBuffer', (buffering) => {
      logEvent('onBuffer', buffering);
    });

    // Error
    player.addEventListener('onError', (error) => {
      logEvent('onError', error.message);
    });
  });

  return (
    <View style={styles.container}>
      <VideoView 
        player={player} 
        style={styles.video}
        controls
        onPictureInPictureChange={(isActive) => {
          logEvent('onPictureInPictureChange', isActive);
        }}
        onFullscreenChange={(isFullscreen) => {
          logEvent('onFullscreenChange', isFullscreen);
        }}
      />

      <View style={styles.eventLog}>
        <Text style={styles.title}>Event Log:</Text>
        {events.map((event, index) => (
          <Text key={index} style={styles.eventText}>
            {event}
          </Text>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  video: {
    width: '100%',
    height: 300,
  },
  eventLog: {
    flex: 1,
    padding: 10,
    backgroundColor: '#f5f5f5',
  },
  title: {
    fontSize: 18,
    fontWeight: 'bold',
    marginBottom: 10,
  },
  eventText: {
    fontSize: 12,
    fontFamily: 'monospace',
    marginBottom: 4,
  },
});

Best Practices

1. Clean Up Event Listeners

Always remove listeners when components unmount:
useEffect(() => {
  const subscription = player.addEventListener('onProgress', (data) => {
    // Handle progress
  });

  return () => {
    subscription.remove();
  };
}, []);

2. Handle Errors Gracefully

player.addEventListener('onError', (error) => {
  // Log error for debugging
  console.error('Playback error:', error);
  
  // Show user-friendly message
  Alert.alert('Video Error', 'Unable to play this video. Please try again.');
  
  // Try to recover or provide alternative
  player.replaceSourceAsync(fallbackVideoUrl);
});

3. Debounce Frequent Events

For events that fire frequently (like onProgress), consider debouncing:
import { debounce } from 'lodash';

const handleProgress = debounce((data) => {
  // Update UI
  setCurrentTime(data.currentTime);
}, 100);

player.addEventListener('onProgress', handleProgress);

4. Use onLoad for Initialization

Wait for onLoad before accessing video metadata:
player.addEventListener('onLoad', (data) => {
  // Safe to access duration, dimensions, etc.
  console.log('Video is', data.duration, 'seconds long');
  
  // Safe to get text tracks
  const tracks = player.getAvailableTextTracks();
});

5. Monitor Buffering for UX

Show loading indicators during buffering:
const [isBuffering, setIsBuffering] = useState(false);

player.addEventListener('onBuffer', (buffering) => {
  setIsBuffering(buffering);
});

// In render:
{isBuffering && <ActivityIndicator />}

Troubleshooting

Events Not Firing

  • Ensure the player has loaded (onLoad must fire first for most events)
  • Check that event listeners are added before the event occurs
  • Verify the event name is spelled correctly

Memory Leaks

  • Always clean up event listeners in useEffect cleanup functions
  • Remove listeners when the player is released

Duplicate Events

  • Avoid adding the same listener multiple times
  • Use useEffect with proper dependencies to control listener registration

Event Reference Summary

EventWhen FiredPayload
onLoadStartVideo starts loading{ sourceType, source }
onLoadMetadata loaded{ duration, width, height, ... }
onReadyToDisplayReady to display-
onPlaybackStateChangePlayback state changes{ isPlaying, isBuffering }
onStatusChangePlayer status changesVideoPlayerStatus
onProgressDuring playback{ currentTime, bufferDuration }
onSeekSeek completesnumber (seek time)
onEndVideo ends-
onBufferBuffering state changesboolean
onBandwidthUpdateBitrate changes{ bitrate, width?, height? }
onVolumeChangeVolume/mute changes{ volume, muted }
onTrackChangeSubtitle track changesTextTrack | null
onErrorError occursVideoRuntimeError

Next Steps

Build docs developers (and LLMs) love