An Expo module for Amazon Interactive Video Service (IVS) player, providing a complete React Native interface matching the official amazon-ivs-react-native-player.
- 🎥 Full IVS player functionality for iOS and Android
- 📱 Picture-in-Picture support
- 🎨 Multiple resize modes (aspectFit, aspectFill, aspectZoom)
- 📊 Quality management and statistics
- 🎮 Complete playback controls
- 📝 Text and metadata cue support
- ⚡ Low latency streaming support
- 🔧 Configurable IVS SDK versions
This module wraps the native Amazon IVS Player SDKs:
- iOS SDK Documentation: https://aws.github.io/amazon-ivs-player-docs/1.43.0/ios/
- Android SDK Documentation: https://aws.github.io/amazon-ivs-player-docs/1.43.0/android/
npm install expo-ivs-player
# or
yarn add expo-ivs-playerAdd to your ios/Podfile or set environment variable to configure IVS SDK version:
ENV['IVS_PLAYER_VERSION'] = '~> 1.30.0' # Optional, defaults to 1.30.0Then run:
cd ios && pod install- iOS 15.1 or higher
- Xcode 14.0 or higher
Add to your android/build.gradle to configure IVS SDK version:
ext {
ivsPlayerVersion = "1.30.0" // Optional, defaults to 1.30.0
}- Android SDK 21 (Lollipop) or higher
- Kotlin 1.6.0 or higher
import IVSPlayer, {
IVSPlayerRef,
PlayerState,
LogLevel,
} from "expo-ivs-player";
import { useRef, useState } from "react";
function App() {
const playerRef = useRef<IVSPlayerRef>(null);
const [paused, setPaused] = useState(false);
const handlePlayerStateChange = (state: PlayerState) => {
console.log("Player state:", state);
switch (state) {
case PlayerState.Playing:
console.log("Stream is playing");
break;
case PlayerState.Buffering:
console.log("Stream is buffering");
break;
case PlayerState.Ended:
console.log("Stream ended");
break;
}
};
const handleError = (error: string) => {
console.error("Player error:", error);
};
return (
<View style={{ flex: 1 }}>
<IVSPlayer
ref={playerRef}
streamUrl="https://your-stream-url.m3u8"
paused={paused}
muted={false}
autoplay={true}
resizeMode="aspectFit"
onPlayerStateChange={handlePlayerStateChange}
onError={handleError}
style={{ width: "100%", height: 300 }}
/>
<Button
title={paused ? "Play" : "Pause"}
onPress={() => setPaused(!paused)}
/>
</View>
);
}import IVSPlayer, { IVSPlayerRef, Quality } from "expo-ivs-player";
function AdvancedPlayer() {
const playerRef = useRef<IVSPlayerRef>(null);
const [qualities, setQualities] = useState<Quality[]>([]);
const [currentQuality, setCurrentQuality] = useState<Quality | null>(null);
const handleDataReady = (data: PlayerData) => {
setQualities(data.qualities);
console.log("Available qualities:", data.qualities);
console.log("Player version:", data.version);
console.log("Session ID:", data.sessionId);
};
const handleQualityChange = (quality: Quality | null) => {
setCurrentQuality(quality);
console.log("Quality changed to:", quality?.name);
};
const selectQuality = (quality: Quality) => {
// Manual quality selection will be implemented in ref methods
console.log("Selected quality:", quality.name);
};
return (
<View style={{ flex: 1 }}>
<IVSPlayer
ref={playerRef}
streamUrl="https://your-stream-url.m3u8"
autoplay={true}
autoQualityMode={true}
onData={handleDataReady}
onQualityChange={handleQualityChange}
style={{ width: "100%", height: 300 }}
/>
{qualities.map((quality) => (
<Button
key={quality.name}
title={`${quality.name} (${quality.bitrate}bps)`}
onPress={() => selectQuality(quality)}
/>
))}
</View>
);
}| Prop | Type | Default | Description | Native Mapping |
|---|---|---|---|---|
streamUrl |
string |
- | The URL of the stream to play | iOS: IVSPlayer.load(URL), Android: Player.load(Uri) |
paused |
boolean |
false |
Controls playback state | iOS: IVSPlayer.state, Android: Player.State |
muted |
boolean |
false |
Controls audio muting | iOS: IVSPlayer.muted, Android: Player.isMuted |
volume |
number |
1.0 |
Volume level (0.0 to 1.0) | iOS: IVSPlayer.volume, Android: Player.volume |
playbackRate |
number |
1.0 |
Playback speed (0.5 to 2.0) | iOS: IVSPlayer.rate, Android: Player.playbackRate |
loop |
boolean |
false |
Enable loop playback | Custom implementation |
autoplay |
boolean |
false |
Autoplay when ready | iOS: IVSPlayer.autoplay, Android: Custom |
| Prop | Type | Default | Description | Native Mapping |
|---|---|---|---|---|
resizeMode |
ResizeMode |
'aspectFit' |
Video scaling mode | iOS: AVLayerVideoGravity, Android: ResizeMode |
showControls |
boolean |
false |
Show native controls | iOS: Custom, Android: PlayerView.setControlsEnabled |
pipEnabled |
boolean |
false |
Enable Picture-in-Picture | iOS: AVPictureInPictureController, Android: PictureInPictureParams |
| Prop | Type | Default | Description | Native Mapping |
|---|---|---|---|---|
autoQualityMode |
boolean |
true |
Auto quality switching | iOS: IVSPlayer.autoQualityMode, Android: Auto quality |
quality |
Quality |
null |
Manual quality selection | iOS: IVSPlayer.quality, Android: Player.quality |
autoMaxQuality |
Quality |
null |
Max quality for auto mode | iOS: IVSPlayer.autoMaxQuality, Android: Custom |
maxBitrate |
number |
0 |
Max bitrate (0 = unlimited) | iOS: IVSPlayer.maxBitrate, Android: Custom |
initialBufferDuration |
number |
0 |
Initial buffer in seconds | iOS: IVSPlayer.initialBufferDuration, Android: Custom |
| Prop | Type | Default | Description | Native Mapping |
|---|---|---|---|---|
liveLowLatency |
boolean |
true |
Enable low latency mode | iOS: IVSPlayer.liveLowLatencyEnabled, Android: Custom |
rebufferToLive |
boolean |
false |
Auto rebuffer to live edge | iOS: IVSPlayer.rebufferToLive(), Android: Custom |
| Prop | Type | Default | Description | Native Mapping |
|---|---|---|---|---|
logLevel |
LogLevel |
'ERROR' |
Logging level | iOS: IVSPlayer.loggingLevel, Android: Logging config |
progressInterval |
number |
1.0 |
Progress update interval (seconds) | Custom timer implementation |
breakpoints |
number[] |
[] |
Time points for events | Custom implementation |
| Event | Parameters | Description | Native Mapping |
|---|---|---|---|
onPlayerStateChange |
(state: PlayerState) |
Player state changed | iOS: IVSPlayer.state, Android: Player.Listener.onStateChanged |
onSeek |
(position: number) |
Seek completed | iOS: seekCompleted, Android: onSeekCompleted |
onProgress |
(progress: number) |
Playback progress | Custom timer |
onTimePoint |
(position: number) |
Breakpoint reached | Custom |
onDurationChange |
(duration: number | null) |
Duration changed | iOS: IVSPlayer.duration, Android: onDurationChanged |
onLoad |
(duration: number | null) |
Stream loaded | iOS: Ready state, Android: READY state |
onLoadStart |
() |
Loading started | Custom |
onRebuffering |
() |
Rebuffering started | iOS: Buffering state, Android: onRebuffering |
| Event | Parameters | Description | Native Mapping |
|---|---|---|---|
onQualityChange |
(quality: Quality | null) |
Quality changed | iOS: IVSPlayer.quality, Android: onQualityChanged |
onData |
(data: PlayerData) |
Player data ready | Native properties |
onVideoStatistics |
(data: VideoData) |
Video stats updated | iOS: IVSPlayer.statistics, Android: Stats |
| Event | Parameters | Description | Native Mapping |
|---|---|---|---|
onLiveLatencyChange |
(latency: number) |
Latency changed | iOS: IVSPlayer.liveLatency, Android: Live latency |
| Event | Parameters | Description | Native Mapping |
|---|---|---|---|
onTextCue |
(cue: TextCue) |
Text cue received | iOS: didOutputCue, Android: onCue(TextCue) |
onTextMetadataCue |
(cue: TextMetadataCue) |
Metadata cue received | iOS: didOutputCue, Android: onCue(TextMetadataCue) |
| Event | Parameters | Description | Native Mapping |
|---|---|---|---|
onError |
(error: string) |
Error occurred | iOS: IVSPlayer.error, Android: onError |
onPipChange |
(isActive: boolean) |
PiP state changed | iOS: AVPictureInPictureControllerDelegate, Android: isInPictureInPictureMode |
const playerRef = useRef<IVSPlayerRef>(null);
// Playback control
playerRef.current?.play();
playerRef.current?.pause();
playerRef.current?.seekTo(30); // Seek to 30 seconds
// Source management
const source = playerRef.current?.preload(url);
playerRef.current?.loadSource(source);
playerRef.current?.releaseSource(source);
// Picture-in-Picture
playerRef.current?.togglePip();
// CORS configuration
playerRef.current?.setOrigin("https://example.com");enum PlayerState {
Idle = "Idle", // Player is idle, no stream loaded
Ready = "Ready", // Stream is loaded and ready
Buffering = "Buffering", // Stream is buffering
Playing = "Playing", // Stream is playing
Ended = "Ended", // Stream has ended
}type ResizeMode =
| "aspectFill" // Scale to fill view, may crop
| "aspectFit" // Scale to fit view, may letterbox
| "aspectZoom"; // Zoom to fill viewenum LogLevel {
IVSLogLevelDebug = "IVSLogLevelDebug",
IVSLogLevelInfo = "IVSLogLevelInfo",
IVSLogLevelWarning = "IVSLogLevelWarning",
IVSLogLevelError = "IVSLogLevelError",
}interface Quality {
name: string; // Quality name (e.g., "720p60")
codecs: string; // Codec information
bitrate: number; // Bitrate in bps
framerate: number; // Frames per second
width: number; // Video width
height: number; // Video height
}interface PlayerData {
qualities: Quality[]; // Available qualities
version: string; // IVS SDK version
sessionId: string; // Session identifier
}interface VideoData {
width: number; // Video width
height: number; // Video height
bitrate?: number; // Current bitrate
framerate?: number; // Current framerate
droppedFrames?: number; // Dropped frame count
}interface TextCue {
type: "text";
text: string;
line?: number;
size?: number;
position?: number;
startTime?: number;
endTime?: number;
}interface TextMetadataCue {
type: "metadata";
text: string;
description?: string;
startTime?: number;
endTime?: number;
}| Platform | Supported | Minimum Version | Notes |
|---|---|---|---|
| iOS | ✅ | iOS 15.1+ | Full support including PiP |
| Android | ✅ | Android SDK 21+ | Full support including PiP |
| Web | - | Basic iframe fallback (limited features) |
# Clean and reinstall
cd ios
rm -rf Pods Podfile.lock
pod install --repo-update- Ensure your iOS deployment target is 15.1 or higher in Xcode
- Verify
use_frameworks! :linkage => :staticis in your Podfile if needed
// In android/build.gradle
ext {
kotlinVersion = "1.6.0" // or higher
}// In android/build.gradle
repositories {
maven { url "https://amazon.bintray.com/ivs" }
}- Check stream URL is valid and accessible
- Verify network permissions in app manifest/Info.plist
- Check console for specific error messages
- Ensure the stream is active (IVS streams stop when not broadcasting)
- Enable
autoQualityModefor automatic quality adjustment - Set appropriate
maxBitratefor device capabilities - Use
liveLowLatencyonly when necessary - Check device performance capabilities
iOS:
- Add
UIBackgroundModeswithaudioin Info.plist - Ensure
pipEnabledprop is set totrue
Android:
- Add PiP permission in AndroidManifest.xml
- Ensure device supports PiP (Android 8.0+)
See the example directory for a complete example app demonstrating:
- Basic playback controls
- Quality selection
- Picture-in-Picture
- Live streaming features
- Error handling
- Statistics display
To run the example:
cd example
npm install
# iOS
cd ios && pod install
npm run ios
# Android
npm run androidThis module provides a compatible API with the official Amazon IVS React Native player. To migrate:
- Replace the import:
// Before
import IVSPlayer from "amazon-ivs-react-native-player";
// After
import IVSPlayer from "expo-ivs-player";- All props and methods remain the same
- Event callbacks maintain the same signatures
- Type definitions are compatible
Contributions are welcome! Please see our contributing guide for details.
MIT
For issues and feature requests, please use the GitHub issues page.
For Amazon IVS specific questions, refer to:
This module provides an Expo-compatible interface matching the amazon-ivs-react-native-player API.