react-native-video

repository·master·Indexed 27 days ago

https://github.com/thewidlarzgroup/react-native-video

An open-source video player component for React Native supporting DRM (Widevine & FairPlay), HLS/DASH streaming, offline playback, and Picture-in-Picture. Version 7 introduces a player-object model using useVideoPlayer and VideoView, requiring React Native 0.75+ and react-native-nitro-modules. Version 6 utilizes a component-with-props model. Includes an optional DRM plugin (@react-native-video/drm) for secure content playback.

Tokens
57.9K
Snippets
139
Records
269
Agent score
93%

What's inside react-native-video

  1. Introduction to React Native Video v7

    master

    React Native Video is a cross-platform video player library for React Native. Version 7 features a new Player API built on Nitro Modules for high-performance, type-safe native communication.

    Key Capabilities:

    • Platforms: Supports iOS, Android, tvOS, and visionOS.
    • Architectures: Compatible with both React Native New and Old Architectures.
    • Features: Supports DRM protection (Widevine and FairPlay), a modular plugin architecture, and offline video playback.
    • Core Foundation: Built on Nitro Modules to ensure high-performance native communication.
  2. Understand the v7 Player-Object Model

    master

    In react-native-video v7, the architecture has shifted from a component-with-props model to a player-object model.

    Key concepts:

    • VideoPlayer: Created via useVideoPlayer(source, setup?) or new VideoPlayer(source). This instance owns all playback state and controls.
    • <VideoView />: An optional display surface that binds to a player. You can perform audio-only playback without using a view.
    • Events: Handled via subscriptions on the player instance (using useEvent or player.addEventListener) rather than JSX callback props. Note that the VideoView maintains its own specific events for fullscreen and PiP lifecycles.
    import { useVideoPlayer, VideoView, useEvent } from 'react-native-video';
    
    const player = useVideoPlayer({ uri: 'https://example.com/master.m3u8' });
    useEvent(player, 'onProgress', ({ currentTime }) => {});
    return <VideoView player={player} controls style={{ flex: 1 }} />;
  3. Understand the react-native-video v6 mental model

    master

    In version 6, react-native-video is implemented as a single imperative <Video> component. You manage the player using three primary mechanisms:

    1. Props: Configure the player state (e.g., source, paused, resizeMode).
    2. Ref: Control the player imperatively using a VideoRef (e.g., calling .seek()).
    3. Callback Props: Handle player events (e.g., onProgress).

    New Architecture Support (v6): Version 6 supports the React Native New Architecture via the interop layer.

    • Requires React Native $\ge$ 0.72.
    • If using React Native versions below 0.74, you must register Video as a legacy component in your react-native.config.js file.
    import Video, { VideoRef } from 'react-native-video';
    import { useRef } from 'react';
    
    const ref = useRef<VideoRef>(null);
    
    // Usage example
    <Video
      ref={ref}
      source={{ uri: 'https://example.com/master.m3u8' }}
      paused={false}
      controls
      resizeMode="contain"
      onProgress={({ currentTime }) => {}}
    />;
    
    // Imperative control via ref
    // ref.current?.seek(30);
  4. Understand the React Native Video Plugin System Architecture

    master

    The plugin system allows you to extend video functionality such as customizing video sources, implementing custom DRM, overriding media factories (Android), reacting to lifecycle events, or controlling caching behavior.

    The architecture relies on three core components:

    • PluginsRegistry: A singleton that manages plugin registration and coordinates interactions.
    • ReactNativeVideoPluginSpec: The interface or protocol defining the contract for all plugins.
    • ReactNativeVideoPlugin: The base implementation providing default behaviors. You should extend this class and override only the methods you need.

    Note: Plugins are automatically registered when you instantiate a class that extends ReactNativeVideoPlugin.

  5. Offline Video SDK Requirements and Supported Formats

    master

    Requirements

    • React Native Video: version 6.15.0 or higher (supports versions 6 and 7).
    • iOS: 15.0 or higher.
    • Android: 6.0 or higher.
    • API Key: A valid key from the SDK platform is required.

    Supported Formats

    FormatiOSAndroid
    HLS
    MP4
    MPEG-DASH

    Key Features

    • HLS/DASH/MP4 Downloading
    • Asset Management
    • Offline DRM (supports DRM-protected content with license handling)
    • Offline Playback
    • Cross-platform (iOS and Android)
  6. Subscribe to player events in v7

    master

    In version 7, events are handled via subscriptions on the player instance rather than through JSX props. There are two primary methods for subscribing:

    1. Using the useEvent hook (Recommended): This automatically removes the subscription when the component unmounts.
    2. Imperative addEventListener: This returns a subscription object with a .remove() method for manual cleanup.

    Note: Subscribing to onError changes the error handling behavior from throwing exceptions to delivering them via the callback. You should always implement an onError listener to prevent playback errors from crashing your application.

    // 1) hook — auto-removed on unmount (recommended)
    useEvent(player, 'onProgress', ({ currentTime }) => {});
    
    // 2) imperative — returns { remove() }
    const sub = player.addEventListener('onEnd', () => {});
    // later: sub.remove();
  7. Use React Native Video on the Web

    master

    React Native Video supports the web platform using video.js (v10). It provides the same useVideoPlayer and VideoView API used on iOS and Android, supporting HLS, subtitles, fullscreen, Picture-in-Picture, and media session controls.

    Setup

    To enable web support, configure your project based on your environment:

    React Native Video manages video.js internally, so no extra CSS imports or manual video.js configuration is required.

    import { useVideoPlayer, VideoView } from 'react-native-video';
    
    function Player() {
      const player = useVideoPlayer({
        uri: 'https://example.com/video.mp4',
      });
    
      return (
        <VideoView
          player={player}
          controls
          style={{ width: '100%', aspectRatio: 16 / 9 }}
        />
      );
    }
  8. Configure react-native-video with Expo

    master

    To use react-native-video in an Expo project, you must use the config plugin in app.json or app.config.js and run expo prebuild. Note: This is not compatible with Expo Go.

    Available v7 plugin options:

    • enableBackgroundAudio: Boolean
    • enableAndroidPictureInPicture: Boolean
    • androidExtensions: Object containing useExoplayerDash and useExoplayerHls (Booleans)
    {
      "plugins": [
        [
          "react-native-video",
          {
            "enableBackgroundAudio": true,
            "enableAndroidPictureInPicture": true
          }
        ]
      ]
    }
  9. Register Analytics Plugins

    master

    Plugins auto-register when they are instantiated. You must instantiate your plugin early in the application lifecycle to ensure it is active when the player is created.

    Android: Instantiate the plugin in MainApplication.kt inside onCreate().

    iOS: Instantiate the plugin in AppDelegate.swift inside didFinishLaunchingWithOptions.

    // Android: MainApplication.kt
    class MainApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            AnalyticsPlugin() // Auto-registers via init block
        }
    }
    // iOS: AppDelegate.swift
    @main
    class AppDelegate: UIResponder, UIApplicationDelegate {
        private var analyticsPlugin: AnalyticsPlugin?
        
        func application(
            _ application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
        ) -> Bool {
            analyticsPlugin = AnalyticsPlugin() // Auto-registers via init
            return true
        }
    }
  10. Implement Manual Analytics on iOS (Swift)

    master

    To integrate custom analytics on iOS, extend ReactNativeVideoPlugin and override onPlayerCreated to set up observers for the AVPlayer. Use Key-Value Observing (KVO) for playback status and rate, and addPeriodicTimeObserver for position tracking. For detailed network and frame metrics, observe .AVPlayerItemNewAccessLogEntry notifications to access the AVPlayerItemAccessLog.

    Key metrics available:

    • Bitrate, Stalls, Dropped frames, Bytes: Extracted from AVPlayerItem.accessLog().
    • Playback status: Observed via player.status (KVO).
    • Play/Pause: Observed via player.rate (KVO).
    • Buffer status: Observed via playerItem.isPlaybackLikelyToKeepUp.
    import AVFoundation
    
    class AnalyticsPlugin: ReactNativeVideoPlugin {
        
        // MARK: - Properties
        
        private weak var currentPlayer: AVPlayer?
        private var rateObserver: NSKeyValueObservation?
        private var statusObserver: NSKeyValueObservation?
        private var timeObserver: Any?
        
        // MARK: - Init
        
        init() {
            super.init(name: "MyAnalytics")
        }
        
        // MARK: - Plugin Lifecycle
        
        override func onPlayerCreated(player: Weak<NativeVideoPlayer>) {
            guard let nativePlayer = player.value else { return }
            currentPlayer = nativePlayer.player
            
            setupPlaybackObservers(for: nativePlayer.player)
            setupQualityTracking(for: nativePlayer.playerItem)
        }
        
        override func onPlayerDestroyed(player: Weak<NativeVideoPlayer>) {
            removeAllObservers()
            flushAnalytics()
        }
        
        // MARK: - Setup
        
        private func setupPlaybackObservers(for player: AVPlayer) {
            rateObserver = player.observe(\.rate) { [weak self] p, _ in
                self?.trackEvent(p.rate > 0 ? "play" : "pause")
            }
            
            statusObserver = player.observe(\.status) { [weak self] p, _ in
                if p.status == .readyToPlay {
                    self?.trackEvent("ready")
                } else if p.status == .failed {
                    self?.trackEvent("error", params: ["message": p.error?.localizedDescription ?? ""])
                }
            }
            
            timeObserver = player.addPeriodicTimeObserver(
                forInterval: CMTime(seconds: 10, preferredTimescale: 1),
                queue: .main
            ) { [weak self] time in
                self?.trackMetric("position", value: time.seconds)
            }
        }
        
        private func setupQualityTracking(for playerItem: AVPlayerItem?) {
            NotificationCenter.default.addObserver(
                self,
                selector: #selector(handleAccessLog),
                name: .AVPlayerItemNewAccessLogEntry,
                object: playerItem
            )
        }
        
        @objc private func handleAccessLog(_ notification: Notification) {
            guard let item = notification.object as? AVPlayerItem,
                  let event = item.accessLog()?.events.last else {
                return
            }
            
            trackMetric("bitrate", value: event.indicatedBitrate)
            trackMetric("stalls", value: Double(event.numberOfStalls))
            trackMetric("dropped_frames", value: Double(event.numberOfDroppedVideoFrames))
        }
        
        private func removeAllObservers() {
            if let observer = timeObserver {
                currentPlayer?.removeTimeObserver(observer)
            }
            rateObserver?.invalidate()
            statusObserver?.invalidate()
            NotificationCenter.default.removeObserver(self)
            
            currentPlayer = nil
            timeObserver = nil
        }
        
        // MARK: - Analytics
        
        private func trackEvent(_ name: String, params: [String: Any] = [:]) {
            // Send to your analytics backend
        }
        
        private func trackMetric(_ name: String, value: Double) {
            // Send to your analytics backend
        }
        
        private func flushAnalytics() {
            // Flush pending analytics
        }
    }
  11. Preload videos for feeds in v7

    master

    Version 7 is optimized for feed-based architectures (like TikTok). You can create players ahead of time and use the preload() method to buffer content before the user reaches it.

    When using the useVideoPlayer hook, you can trigger preloading within the setup callback, which runs once when loading starts.

    // preload in the setup callback (runs once when loading starts)
    const next = useVideoPlayer({ uri: nextUrl }, (player) => player.preload());
    
    // when the user swipes, mount/show its <VideoView> and call next.play()