npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@zezosoft/zezo-ott-react-native-video-player

v1.0.2

Published

Production-ready React Native OTT video player library for Android & iOS. Features: playlists, seasons, auto-next playback, subtitles (SRT/VTT), custom theming, analytics tracking, fullscreen mode, gesture controls, ads player (pre-roll/mid-roll/post-roll

Downloads

137

Readme

🎬 ZezoOTT Video Player

A powerful, production-ready React Native video player component designed specifically for OTT (Over-The-Top) platforms. Built with TypeScript and featuring a centralized state management system, it delivers a seamless full-screen video playback experience with comprehensive support for playlists, seasons, ads, analytics, and custom controls.


📋 Table of Contents


✨ Features

  • 📺 Episode Navigation & Seasons - Seamless navigation between episodes and seasons
  • 📊 Watch Progress Tracking - Built-in analytics for watch time and progress
  • 🎨 Custom Theming - Fully customizable UI colors and metrics
  • 🔄 Auto-Next Playback - Automatically plays next episode/video
  • 🎬 Playlist Integration - Support for playlists and multi-season content
  • 🗄️ Centralized Store - Zustand-powered state management for playback, tracks & UI
  • 📱 React Native Optimized - Built specifically for React Native OTT apps
  • 📢 Ads Player - Pre-roll, mid-roll, and post-roll ads with comprehensive tracking
  • 🎯 Production Ready - Minimal configuration, battle-tested in production
  • 🔒 TypeScript - Full TypeScript support with comprehensive type definitions

📦 Requirements

Peer Dependencies

This package requires the following peer dependencies to be installed in your project:

yarn add react react-native react-native-video react-native-safe-area-context \
  react-native-gesture-handler react-native-reanimated react-native-fast-image \
  react-native-linear-gradient react-native-svg

or

npm install react react-native react-native-video react-native-safe-area-context \
  react-native-gesture-handler react-native-reanimated react-native-fast-image \
  react-native-linear-gradient react-native-svg

Additional Setup

For fullscreen functionality, you'll also need:

yarn add react-native-orientation-locker

See Troubleshooting section for setup instructions.


📦 Installation

yarn add @zezosoft/zezo-ott-react-native-video-player

or

npm install @zezosoft/zezo-ott-react-native-video-player

🚀 Quick Start

import React from 'react';
import { VideoPlayer } from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function VideoPlayerScreen() {
  const insets = useSafeAreaInsets();

  return (
    <VideoPlayer
      insets={insets}
      isFocused={true}
      mode="fullscreen"
      autoNext={true}
      onClose={() => console.log('Player closed')}
      onWatchProgress={(progress) => {
        console.log('Watch Progress:', progress);
      }}
    />
  );
}

Note: Make sure you've set up the video player store with your content before rendering the player. See Playlist & Seasons section for details.


📚 API Reference

⚙️ VideoPlayer Props

| Prop | Type | Required | Default | Description | | ------------------- | ------------------------------------------------------------------------------------------- | -------- | -------------- | -------------------------------------------------------------------------------------- | | insets | EdgeInsets | ✅ Yes | - | Safe area insets from react-native-safe-area-context (use useSafeAreaInsets()). | | onClose | () => void | ❌ No | - | Callback triggered when the player is closed. | | isFocused | boolean | ❌ No | true | Controls playback focus. Automatically pauses when the screen is not active. | | seekTime | number \| null | ❌ No | null | Starts playback from a given time (in seconds). | | mode | 'fullscreen' \| 'normal' | ❌ No | 'fullscreen' | Sets the display mode of the player. | | autoNext | boolean | ❌ No | true | Automatically plays the next episode/video after completion. | | event | { onPressEpisode?: ({ episode }: { episode: MediaEpisode }) => Promise<boolean> } | ❌ No | - | Event hooks for custom handling (e.g. episode selection, subscription checks). | | theme | Partial<VideoPlayerTheme> | ❌ No | - | Customize the look & feel of the player (colors, metrics, etc.). | | onWatchProgress | (progress: ExtendedWatchProgress) => void | ❌ No | - | Reports playback analytics such as watch time, current time, and completion. | | ads | VideoAd[] | ❌ No | [] | Array of video ads to display (pre-roll, mid-roll, post-roll). | | onAdEnd | (ad: VideoAd) => void | ❌ No | - | Callback fired when an ad finishes playing. | | onAdError | (error: Error, ad: VideoAd) => void | ❌ No | - | Callback fired when an ad encounters an error. | | onAdTracking | ({ ad, trackingUrl, event }: { ad: VideoAd; trackingUrl: string; event: string }) => void | ❌ No | - | Callback for ad tracking events (impression, start, quartiles, complete, skip, click). |

📢 VideoAd Interface

The VideoAd interface defines the structure for video advertisements:

interface VideoAd {
  id: string; // Unique identifier for the ad
  title: string; // Ad title
  description: string; // Ad description
  position: 'pre' | 'mid' | 'post'; // Ad position: pre-roll, mid-roll, or post-roll
  source: string; // Video source URL (MP4 or HLS)
  duration: number; // Ad duration in seconds
  time?: number; // For mid-roll ads: time in seconds when ad should play
  skippable: boolean; // Whether the ad can be skipped
  skipAfter: number; // Seconds after which skip button appears (if skippable)
  clickThroughUrl?: string; // Optional URL to open when ad is clicked
  tracking?: AdTracking; // Optional tracking URLs for ad events
}

interface AdTracking {
  impression?: string; // Fired when ad is displayed
  start?: string; // Fired when ad playback starts
  firstQuartile?: string; // Fired at 25% of ad duration
  midpoint?: string; // Fired at 50% of ad duration
  thirdQuartile?: string; // Fired at 75% of ad duration
  complete?: string; // Fired when ad completes
  skip?: string; // Fired when ad is skipped
  click?: string; // Fired when ad is clicked
}

Ad Position Types:

  • 'pre' - Pre-roll ad: Plays before the main video content
  • 'mid' - Mid-roll ad: Plays during the video at the specified time
  • 'post' - Post-roll ad: Plays after the main video content completes

🚀 Usage Examples

▶️ Basic Example

import React from 'react';
import { VideoPlayer } from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function App() {
  const insets = useSafeAreaInsets();

  return (
    <VideoPlayer
      insets={insets}
      isFocused={true}
      onClose={() => console.log('Player closed')}
      autoNext={true}
      onWatchProgress={(progress) => {
        console.log('Watch Progress:', progress);
      }}
    />
  );
}

🔄 Auto-Next Episodes

import React from 'react';
import { VideoPlayer } from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function VideoPlayerScreen() {
  const insets = useSafeAreaInsets();

  return (
    <VideoPlayer
      insets={insets}
      autoNext={true}
      event={{
        onPressEpisode: async ({ episode }) => {
          console.log('Next episode:', episode.title);
          // Add your custom logic here (e.g., subscription check)
          // return false to block playback
          return true;
        },
      }}
    />
  );
}

🎨 Custom Theming

import React from 'react';
import { VideoPlayer } from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function ThemedPlayer() {
  const insets = useSafeAreaInsets();

  return (
    <VideoPlayer
      insets={insets}
      theme={{
        colors: {
          primary: '#E50914',
          background: '#000000',
          onBackground: '#FFFFFF',
        },
        metrics: {
          controlButtonSize: 42,
        },
      }}
    />
  );
}

📺 Playlist & Seasons Setup

// ContentDetailsScreen.tsx
import { useVideoPlayerStore } from '@zezosoft/zezo-ott-react-native-video-player';
import { useNavigation } from '@react-navigation/native';

export default function ContentDetailsScreen({ contentData }) {
  const {
    resetStore,
    setPlayList,
    setContentSeasons,
    setActiveSeason,
    setCurrentTrackIndex,
    setActiveTrack,
  } = useVideoPlayerStore();
  const navigation = useNavigation();

  const handlePlay = () => {
    // Always reset store before loading new content
    resetStore();

    // Set seasons data
    setContentSeasons(contentData.seasons);
    setActiveSeason(contentData.seasons[0]);

    // Create playlist from episodes
    const playlist = contentData.seasons[0].episodes.map((ep) => ({
      id: ep.id,
      title: ep.name,
      contentId: contentData.id,
      sourceLink: ep.sourceLink,
      type: contentData.type,
    }));

    // Set playlist and active track
    setPlayList(playlist);
    setCurrentTrackIndex(0);
    setActiveTrack(playlist[0]);

    // Navigate to player screen
    navigation.navigate('VideoPlayerScreen');
  };

  return <Button title="Play" onPress={handlePlay} />;
}
// VideoPlayerScreen.tsx
import { VideoPlayer } from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function VideoPlayerScreen() {
  const insets = useSafeAreaInsets();

  return <VideoPlayer insets={insets} isFocused={true} autoNext={true} />;
}

📢 Ads Player

import React from 'react';
import {
  VideoPlayer,
  type VideoAd,
} from '@zezosoft/zezo-ott-react-native-video-player';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

export default function VideoPlayerWithAds() {
  const insets = useSafeAreaInsets();

  const ads: VideoAd[] = [
    {
      id: 'ad1',
      title: 'Pre-roll Ad',
      description: 'Advertisement',
      position: 'pre',
      source: 'https://example.com/ad.mp4',
      duration: 30,
      skippable: true,
      skipAfter: 5,
      clickThroughUrl: 'https://example.com',
      tracking: {
        impression: 'https://example.com/track/impression',
        start: 'https://example.com/track/start',
        firstQuartile: 'https://example.com/track/firstQuartile',
        midpoint: 'https://example.com/track/midpoint',
        thirdQuartile: 'https://example.com/track/thirdQuartile',
        complete: 'https://example.com/track/complete',
        skip: 'https://example.com/track/skip',
        click: 'https://example.com/track/click',
      },
    },
    {
      id: 'ad2',
      title: 'Mid-roll Ad',
      description: 'Advertisement',
      position: 'mid',
      source: 'https://example.com/mid-ad.mp4',
      duration: 15,
      time: 300, // Show at 5 minutes (300 seconds)
      skippable: false,
      skipAfter: 0,
      tracking: {
        impression: 'https://example.com/track/mid/impression',
        complete: 'https://example.com/track/mid/complete',
      },
    },
    {
      id: 'ad3',
      title: 'Post-roll Ad',
      description: 'Advertisement',
      position: 'post',
      source: 'https://example.com/post-ad.mp4',
      duration: 20,
      skippable: true,
      skipAfter: 10,
      tracking: {
        impression: 'https://example.com/track/post/impression',
        complete: 'https://example.com/track/post/complete',
      },
    },
  ];

  return (
    <VideoPlayer
      insets={insets}
      isFocused={true}
      ads={ads}
      onAdEnd={(ad) => {
        console.log('Ad finished:', ad.title);
      }}
      onAdError={(error, ad) => {
        console.error('Ad error:', error, ad.title);
      }}
      onAdTracking={({ ad, trackingUrl, event }) => {
        console.log('Ad tracking:', event, trackingUrl);
        // Make tracking request to your ad server
        fetch(trackingUrl).catch(console.error);
      }}
      onClose={() => console.log('Player closed')}
    />
  );
}

🗄️ Video Player Store

The VideoPlayerStore is a centralized Zustand store that powers the ZezoOTT Video Player. It manages playback state, tracks, UI controls, analytics, and content navigation.

🚀 Store Features

  • Playback State: current time, duration, buffering, playback rate
  • Tracks Management: audio tracks, subtitles, video quality
  • UI State: controls visibility, settings modal, skip/next episode indicators
  • Content Structure: playlists, seasons, active season/episode
  • Analytics: watch time tracking, view count, error handling

⚡ Store Usage Example

import { useVideoPlayerStore } from '@zezosoft/zezo-ott-react-native-video-player';
import React from 'react';

export default function VideoPlayerExample() {
  const {
    setPlayList,
    setActiveSeason,
    setActiveTrack,
    setContentSeasons,
    setCurrentTrackIndex,
    resetStore,
    playList,
    activeTrack,
    activeSeason,
  } = useVideoPlayerStore();

  const contentData = {
    id: 'movie123',
    type: 'series',
    name: 'My Series',
    seasons: [
      {
        id: 'season1',
        name: 'Season 1',
        seasonNumber: 1,
        episodes: [
          {
            id: 'ep1',
            name: 'Episode 1',
            sourceLink: 'https://example.com/ep1.mp4',
          },
          {
            id: 'ep2',
            name: 'Episode 2',
            sourceLink: 'https://example.com/ep2.mp4',
          },
        ],
      },
    ],
  };

  const setupPlayer = () => {
    resetStore();
    setContentSeasons(contentData.seasons);
    setActiveSeason(contentData.seasons[0]);

    const playlist = contentData.seasons[0].episodes.map((ep) => ({
      id: ep.id,
      title: ep.name,
      contentId: contentData.id,
      sourceLink: ep.sourceLink,
      type: contentData.type,
    }));

    setPlayList(playlist);
    setCurrentTrackIndex(0);
    setActiveTrack(playlist[0]);
  };

  return (
    <div>
      <button onClick={setupPlayer}>Load Series</button>
      <h3>Active Season: {activeSeason?.name}</h3>
      <ul>
        {playList.map((track) => (
          <li key={track.id}>
            {track.title} {activeTrack?.id === track.id ? '(Playing)' : ''}
          </li>
        ))}
      </ul>
    </div>
  );
}

📝 Best Practices

  • 🔄 Always call resetStore() before loading new content to prevent state conflicts
  • 🎚️ Playback rate requires both rate and label to be in sync
  • 🕹️ Controls auto-hide is managed by controlsTimer in the store
  • 🌐 Track selections depend on available OnLoadData — always null-check before using
  • 📊 Analytics props integrate seamlessly with your analytics pipeline
  • ⚠️ Error handling: Use setError from the store for surfacing playback issues
  • 🎬 Seasons & episodes: Update both contentSeasons and activeSeason before setting playlist
  • 📢 Ads: Ensure ad sources are valid and tracking URLs are properly configured
  • 🔒 Type safety: Leverage TypeScript types for better development experience

🔧 Troubleshooting

Fullscreen Not Working

If fullscreen doesn't work, ensure you have installed react-native-orientation-locker:

yarn add react-native-orientation-locker
# or
npm install react-native-orientation-locker

Known Issues

If you encounter any problems with react-native-orientation-locker, check the official issues page: View Known Issues

Setup & Installation

Follow the official guide to properly set up react-native-orientation-locker: View Setup Guide

Common Issues

  1. Player not showing: Ensure you've set up the store with activeTrack before rendering
  2. Ads not playing: Verify ad sources are accessible and position is correctly set
  3. Controls not visible: Check isFocused prop and ensure screen is active
  4. TypeScript errors: Make sure all peer dependencies are installed

🔗 Demo App

Check out our demo app to see the player in action:

👉 Zezo OTT Demo App


👨‍💻 Developer

Made with passion by:


📄 License

Licensed under the MIT License.