@zezosoft/zezo-ott-react-native-video-player
Version:
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
578 lines (467 loc) âĸ 19.2 kB
Markdown
# đŦ 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](#-features)
- [Requirements](#-requirements)
- [Installation](#-installation)
- [Quick Start](#-quick-start)
- [API Reference](#-api-reference)
- [VideoPlayer Props](#-videoplayer-props)
- [VideoAd Interface](#-videoad-interface)
- [Usage Examples](#-usage-examples)
- [Basic Example](#-basic-example)
- [Auto-Next Episodes](#-auto-next-episodes)
- [Custom Theming](#-custom-theming)
- [Playlist & Seasons](#-playlist--seasons)
- [Ads Player](#-ads-player)
- [Video Player Store](#-video-player-store)
- [Best Practices](#-best-practices)
- [Troubleshooting](#-troubleshooting)
- [Demo App](#-demo-app)
- [License](#-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:
```bash
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
```bash
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:
```bash
yarn add react-native-orientation-locker
```
See [Troubleshooting](#-troubleshooting) section for setup instructions.
## đĻ Installation
```bash
yarn add @zezosoft/zezo-ott-react-native-video-player
```
or
```bash
npm install @zezosoft/zezo-ott-react-native-video-player
```
## đ Quick Start
```tsx
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](#-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:
```typescript
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
```tsx
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
```tsx
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
```tsx
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
```tsx
// 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} />;
}
```
```tsx
// 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
```tsx
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
```tsx
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**:
```bash
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](https://github.com/wonday/react-native-orientation-locker/issues)
#### Setup & Installation
Follow the official guide to properly set up react-native-orientation-locker:
[View Setup Guide](https://github.com/wonday/react-native-orientation-locker)
### 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](https://github.com/Zezo-Soft/zezo-ott-demo-app)
## đ¨âđģ Developer
Made with passion by:
<p>
<a href="https://github.com/Naresh-Dhamu" target="_blank">
<img src="https://avatars.githubusercontent.com/u/89912059?v=4" width="80" height="80" alt="Naresh Dhamu" style="border-radius: 50%; margin-left:10px">
<br />
<strong>Naresh Dhamu</strong>
</a>
</p>
## đ License
Licensed under the [MIT License](LICENSE).
<p align="center">
⥠Powered by <a href="https://zezosoft.com" target="_blank"><strong>ZezoSoft</strong></a>
</p>