@ibad9/react-native-video-player
v1.0.0
Published
YouTube-grade native video player for React Native with fully native controls. ExoPlayer (Android) + AVPlayer (iOS). HLS, DASH, DRM, IMA ads, Google Cast, AirPlay, PiP.
Maintainers
Readme
@ibad9/react-native-video-player
YouTube-grade native video player for React Native with fully native controls. Zero JS-side UI rendering — all controls (play/pause, seek, quality, speed, captions, fullscreen, PiP, Cast, AirPlay) are rendered natively for maximum performance.
Features
| Feature | Android | iOS | |---------|---------|-----| | HLS streaming | ✅ | ✅ | | DASH streaming | ✅ | ❌ (iOS limitation) | | DRM | Widevine / ClearKey | FairPlay | | IMA pre-roll ads | ✅ | ✅ | | Google Cast | ✅ | ✅ | | AirPlay | ❌ | ✅ | | Picture-in-Picture | ✅ | ✅ | | Double-tap seek | ✅ | ✅ | | Long-press 2x speed | ✅ | ✅ | | Quality selector | ✅ | ✅ | | Playback speed | ✅ | ✅ | | Captions / subtitles | ✅ | ✅ | | Watermark overlay | ✅ | ✅ | | Fullscreen (button + rotation) | ✅ | ✅ | | Error recovery (silent retry) | ✅ | ✅ | | Live streams (LIVE badge) | ✅ | ✅ |
Installation
npm install @ibad9/react-native-video-player
# or
yarn add @ibad9/react-native-video-playeriOS
cd ios && pod installAndroid
No additional steps — autolinking handles everything.
Required App-Level Configuration
Android — AndroidManifest.xml
Add these to your <application> tag:
<!-- Google Cast options provider -->
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.nativevideoplayer.feature.CastOptionsProvider" />Add to your <activity> tag (for PiP support):
<activity
...
android:supportsPictureInPicture="true"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode">iOS — AppDelegate.swift
Initialize Google Cast before React Native starts:
import GoogleCast
func application(_ application: UIApplication, didFinishLaunchingWithOptions ...) -> Bool {
// Initialize Google Cast
let castOptions = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: kGCKDefaultMediaReceiverApplicationID))
castOptions.physicalVolumeButtonsWillControlDeviceVolume = true
GCKCastContext.setSharedInstanceWith(castOptions)
// ... rest of setup
}iOS — Info.plist
For PiP and background audio, add:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>For HTTP streams (non-HTTPS), add the domain to App Transport Security:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>Usage
import NativeVideoPlayer from '@ibad9/react-native-video-player';
import type { NativeVideoPlayerRef } from '@ibad9/react-native-video-player';
const playerRef = useRef<NativeVideoPlayerRef>(null);
<NativeVideoPlayer
ref={playerRef}
source={{
uri: 'https://example.com/stream.m3u8',
metadata: { title: 'My Video', artist: 'Channel Name' },
}}
isLive={false}
pipEnabled={true}
castEnabled={true}
adTagUrl={`https://pubads.g.doubleclick.net/gampad/ads?...&correlator=${Date.now()}`}
watermarkUrl="https://example.com/logo.png"
onLoad={(data) => console.log('Duration:', data.duration)}
onError={(data) => console.error(data.error.message)}
onEnd={() => console.log('Playback ended')}
style={{ width: '100%', aspectRatio: 16 / 9, backgroundColor: '#000' }}
/>
// Imperative methods
playerRef.current?.seek(30000); // seek to 30s
playerRef.current?.destroy(); // release playerProps
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| source | { uri, headers?, startPosition?, metadata? } | required | Media source |
| drm | { type, licenseServer, headers? } | — | DRM config (widevine/clearkey/fairplay) |
| textTracks | { uri, language, title, mimeType }[] | — | Sidecar subtitle tracks |
| adTagUrl | string | — | VAST/VMAP ad tag URL (enables IMA) |
| isLive | boolean | false | Low-latency buffer + LIVE badge |
| pipEnabled | boolean | false | Enable Picture-in-Picture |
| castEnabled | boolean | false | Show Google Cast button |
| castReceiverAppId | string | — | Custom Cast receiver app ID |
| bufferConfig | { minBufferMs?, maxBufferMs?, ... } | — | Custom buffer config |
| watermarkUrl | string | — | URL to watermark image (top-right) |
Events
| Event | Payload | Description |
|-------|---------|-------------|
| onLoad | { duration, naturalSize, audioTracks, textTracks, videoTracks } | Media loaded |
| onProgress | { currentTime, playableDuration } | Every 250ms |
| onEnd | — | Playback ended |
| onError | { error: { code, message, isFatal } } | Error occurred |
| onPlaybackStateChanged | { isPlaying } | Play/pause |
| onPipChanged | { isActive } | PiP entered/exited |
| onCastStateChanged | { state, deviceName? } | Cast connection |
| onAdEvent | { event, adData? } | IMA ad lifecycle |
| onAudioBecomingNoisy | — | Headphones unplugged |
| onAudioFocusChanged | { hasAudioFocus } | Audio focus change |
Ref Methods
| Method | Description |
|--------|-------------|
| seek(positionMs) | Seek to position |
| destroy() | Release player resources |
License
MIT
