UNPKG

@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
# đŸŽŦ 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>