Introduction
Components
- Accordion
- Action Sheet
- Alert
- Audio Player
- Audio Recorder
- Audio Waveform
- Avatar
- Badge
- BottomSheet
- Button
- Camera
- Camera Preview
- Card
- Carousel
- Checkbox
- Collapsible
- Color Picker
- Combobox
- Date Picker
- File Picker
- Gallery
- Hello Wave
- Icon
- Image
- Input
- Input OTP
- Link
- MediaPicker
- Mode Toggle
- Onboarding
- ParallaxScrollView
- Picker
- Popover
- Progress
- Radio
- ScrollView
- SearchBar
- Separator
- Share
- Sheet
- Skeleton
- Spinner
- Switch
- Table
- Tabs
- Text
- Toast
- Toggle
- Video
- View
Charts
import { Video } from '@/components/ui/video';
export function VideoDemo() {
return (
<Video
source={{
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
}}
style={{
width: '100%',
height: 200,
borderRadius: 8,
}}
autoPlay={false}
loop={false}
muted={false}
showControls={true}
/>
);
}
Installation
pnpm dlx bna-ui add video
Usage
import { Video } from '@/components/ui/video';
<Video
source={{ uri: 'https://example.com/video.mp4' }}
autoPlay={true}
showControls={true}
/>
Examples
Default
import { Video } from '@/components/ui/video';
export function VideoDemo() {
return (
<Video
source={{
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
}}
style={{
width: '100%',
height: 200,
borderRadius: 8,
}}
autoPlay={false}
loop={false}
muted={false}
showControls={true}
/>
);
}
Native Controls
import { Video } from '@/components/ui/video';
import React from 'react';
export function VideoNativeControls() {
return (
<Video
source={{
uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
}}
style={{
width: '100%',
height: 200,
borderRadius: 8,
}}
nativeControls={true}
autoPlay={false}
loop={false}
/>
);
}
Custom Controls
import { Video } from '@/components/ui/video';
import React from 'react';
export function VideoCustomControls() {
return (
<Video
source={{
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
}}
style={{
width: '100%',
height: 250,
borderRadius: 12,
}}
nativeControls={false}
showControls={true}
autoPlay={false}
loop={true}
seekBy={5}
onPlaybackStatusUpdate={(status) => {
console.log('Playback status:', status);
}}
onLoad={() => {
console.log('Video loaded successfully');
}}
/>
);
}
With Subtitles
import { Video } from '@/components/ui/video';
import React from 'react';
export function VideoSubtitles() {
const subtitles = [
{ start: 0, end: 3, text: 'Welcome to our video demo' },
{ start: 3, end: 6, text: 'This video shows subtitle support' },
{ start: 6, end: 9, text: 'Subtitles appear at the bottom' },
{ start: 9, end: 12, text: 'They automatically sync with playback' },
{ start: 12, end: 15, text: 'Perfect for accessibility!' },
];
return (
<Video
source={{
uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/SubaruOutbackOnStreetAndDirt.mp4',
}}
style={{
width: '100%',
height: 200,
borderRadius: 8,
}}
subtitles={subtitles}
autoPlay={true}
loop={true}
showControls={true}
/>
);
}
Autoplay & Loop
import { Video } from '@/components/ui/video';
import React from 'react';
export function VideoAutoplayLoop() {
return (
<Video
source={{
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
}}
style={{
width: '100%',
height: 180,
borderRadius: 8,
}}
autoPlay={true}
loop={true}
muted={true}
showControls={true}
contentFit='cover'
/>
);
}
Different Sources
import { Text } from '@/components/ui/text';
import { Video } from '@/components/ui/video';
import { View } from '@/components/ui/view';
import React from 'react';
export function VideoSources() {
const videoSources = [
{
title: 'MP4 Source',
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
},
{
title: 'Alternative MP4',
uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
},
];
return (
<View style={{ gap: 16 }}>
{videoSources.map((source, index) => (
<View key={index} style={{ gap: 8 }}>
<Text variant='body' style={{ fontWeight: '600' }}>
{source.title}
</Text>
<Video
source={{ uri: source.uri }}
style={{
width: '100%',
height: 160,
borderRadius: 8,
}}
autoPlay={false}
showControls={true}
/>
</View>
))}
</View>
);
}
Gesture Controls
import { Text } from '@/components/ui/text';
import { Video } from '@/components/ui/video';
import { View } from '@/components/ui/view';
import React from 'react';
export function VideoGestures() {
return (
<View style={{ gap: 12 }}>
<Text variant='body' style={{ fontSize: 14, opacity: 0.8 }}>
Tap center to play/pause • Tap left to seek back • Tap right to seek
forward
</Text>
<Video
source={{
uri: 'https://bna-ui.s3.eu-north-1.amazonaws.com/',
}}
style={{
width: '100%',
height: 220,
borderRadius: 12,
}}
seekBy={10}
autoPlay={false}
showControls={true}
onPlaybackStatusUpdate={(status) => {
// Handle playback updates
}}
/>
<Text variant='caption' style={{ textAlign: 'center', opacity: 0.6 }}>
Try the gesture controls! This video seeks by 10 seconds.
</Text>
</View>
);
}
Content Fit Options
import { Text } from '@/components/ui/text';
import { Video } from '@/components/ui/video';
import { View } from '@/components/ui/view';
import React from 'react';
export function VideoContentFit() {
const contentFitOptions: Array<{
mode: 'contain' | 'cover' | 'fill';
description: string;
}> = [
{ mode: 'contain', description: 'Fit entirely within bounds' },
{ mode: 'cover', description: 'Fill bounds, may crop' },
{ mode: 'fill', description: 'Stretch to fill bounds' },
];
return (
<View style={{ gap: 16 }}>
{contentFitOptions.map((option, index) => (
<View key={index} style={{ gap: 8 }}>
<View>
<Text variant='body' style={{ fontWeight: '600' }}>
{option.mode.charAt(0).toUpperCase() + option.mode.slice(1)}
</Text>
<Text variant='caption' style={{ opacity: 0.7 }}>
{option.description}
</Text>
</View>
<Video
source={{
uri: 'https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/TearsOfSteel.mp4',
}}
style={{
width: '100%',
height: 120,
borderRadius: 8,
backgroundColor: '#f0f0f0',
}}
contentFit={option.mode}
autoPlay={false}
showControls={true}
muted={true}
/>
</View>
))}
</View>
);
}
API Reference
Video
The main video player component with customizable controls and features.
Prop | Type | Default | Description |
---|---|---|---|
source | VideoSource | - | The video source (required). |
style | ViewStyle | - | Additional styles for the video container. |
seekBy | number | 2 | Seconds to seek by on double tap. |
autoPlay | boolean | false | Whether to auto-play the video. |
loop | boolean | false | Whether to loop the video. |
muted | boolean | false | Whether to start muted. |
nativeControls | boolean | false | Use native video controls instead of custom ones. |
showControls | boolean | true | Whether to show custom controls. |
allowsFullscreen | boolean | true | Allow fullscreen mode. |
allowsPictureInPicture | boolean | true | Allow picture-in-picture mode. |
contentFit | 'contain' | 'cover' | 'fill' | 'cover' | How the video should fit within its container. |
onLoad | () => void | - | Callback when video is loaded. |
onError | (error: any) => void | - | Callback when an error occurs. |
onPlaybackStatusUpdate | (status: any) => void | - | Callback for playback status updates. |
onFullscreenUpdate | (isFullscreen: boolean) => void | - | Callback when fullscreen state changes. |
subtitles | Array<Subtitle> | [] | Array of subtitle objects. |
VideoRef
Reference methods available on the Video component.
Method | Type | Description |
---|---|---|
play | () => void | Start playing the video. |
pause | () => void | Pause the video. |
seekTo | (seconds: number) => void | Seek to a specific time in seconds. |
setVolume | (volume: number) => void | Set the volume (0-1). |
getCurrentTime | () => number | Get the current playback time. |
getDuration | () => number | Get the total duration of the video. |
isPlaying | () => boolean | Check if the video is currently playing. |
isMuted | () => boolean | Check if the video is muted. |
Subtitle
Subtitle object structure for the subtitles prop.
Property | Type | Description |
---|---|---|
start | number | Start time in seconds for the subtitle. |
end | number | End time in seconds for the subtitle. |
text | string | The subtitle text to display. |
Gestures
The Video component supports several gesture interactions:
- Single tap (center): Play/pause toggle
- Single tap (left side): Seek backward by
seekBy
seconds - Single tap (right side): Seek forward by
seekBy
seconds - Progress bar tap: Seek to specific position
Features
Custom Controls
- Play/pause button with visual feedback
- Progress bar with seek functionality
- Time display (current/total)
- Mute/unmute toggle
- Auto-hide controls after 3 seconds
Subtitle Support
- Display subtitles based on current playback time
- Customizable subtitle styling
- Automatic subtitle timing
Error Handling
- Graceful error handling with fallbacks
- Console logging for debugging
- Error callbacks for custom handling
Accessibility
The Video component is built with accessibility in mind:
- Touch targets meet minimum size requirements
- Clear visual feedback for interactions
- Subtitle support for hearing accessibility
- Proper semantic structure for screen readers
Performance Considerations
- Efficient playback status updates (100ms intervals)
- Optimized gesture handling
- Memory cleanup on component unmount
- Smooth animations with native driver when possible