@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
Maintainers
Keywords
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
- Requirements
- Installation
- Quick Start
- API Reference
- Usage Examples
- Video Player Store
- Best Practices
- Troubleshooting
- Demo App
- License
✨ 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-svgor
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-svgAdditional Setup
For fullscreen functionality, you'll also need:
yarn add react-native-orientation-lockerSee Troubleshooting section for setup instructions.
📦 Installation
yarn add @zezosoft/zezo-ott-react-native-video-playeror
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 specifiedtime'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
rateandlabelto be in sync - 🕹️ Controls auto-hide is managed by
controlsTimerin the store - 🌐 Track selections depend on available
OnLoadData— always null-check before using - 📊 Analytics props integrate seamlessly with your analytics pipeline
- ⚠️ Error handling: Use
setErrorfrom the store for surfacing playback issues - 🎬 Seasons & episodes: Update both
contentSeasonsandactiveSeasonbefore 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-lockerKnown 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
- Player not showing: Ensure you've set up the store with
activeTrackbefore rendering - Ads not playing: Verify ad sources are accessible and
positionis correctly set - Controls not visible: Check
isFocusedprop and ensure screen is active - TypeScript errors: Make sure all peer dependencies are installed
🔗 Demo App
Check out our demo app to see the player in action:
👨💻 Developer
Made with passion by:
📄 License
Licensed under the MIT License.
