@rsplayer/rsplayer
v0.3.0
Published
Native audio player and video-only view for React Native media sessions.
Maintainers
Readme
@rsplayer/rsplayer
Native audio player and video-only view for React Native.
RSPlayer is a small native audio module for streaming audio with background
playback and system media controls. The package also exports RSVideo, a
video-only native view for muted visual clips that should not compete with the
active audio session.
Support
rsplayer is free to use.
If this package helped you or saved you time, you can support the project here:
PayPal: [email protected]
Thank you for supporting the first versions of rsplayer.
Install
npm install @rsplayer/rsplayerOr install directly from GitHub:
npm install github:ruslansavustian/RSPlayerFor iOS:
cd ios && pod installChangelog
0.3.0
- Added
RSVideo, a native video-only view for muted timeline/session clips. Use it when video is only visual context and audio is handled separately byRSPlayer. RSVideois designed for exercise timelines, guided sessions, previews, and similar screens where a muted clip should play behind or beside an active soundtrack, voice-over, or cue flow.- Android
RSVideodisables ExoPlayer audio tracks throughDefaultTrackSelectorand callssetAudioAttributes(..., false), so muted visual clips do not request Android audio focus from the activeRSPlayersession. - iOS
RSVideousesAVPlayerLayer, removes audible media selection, and disablesAVPlayerItemaudio tracks so visual clips do not interfere with the app audio session. RSVideosupports the focused subset needed for visual playback:source,paused,muted,resizeMode, load/buffer/error callbacks, andref.current?.seek(seconds).- Android
RSVideoforces a secondPlayerViewlayout pass when resize mode or video size changes, matchingreact-native-videocover behavior and avoiding vertically shifted crops in full-screen timeline views.
0.2.3
- Fixed Android cue playback stealing audio focus from the main player.
playCuenow keeps the active soundtrack/session alive while short voice prompts play on the secondary cue player. - Fixed Android seeking for progressive MP3/audio streams that ExoPlayer
previously reported as non-seekable. Android media sources now enable
constant-bitrate extractor seeking, so
seekToand controllerseekBycan jump to the requested position instead of restarting from the beginning. - Improved managed controller
seekByaccuracy by reading the current native player state before applying the relative seek offset. - Android progress is now emitted after ExoPlayer reports the seek position
change, avoiding stale progress immediately after
seekTo.
Requirements
- React Native
>= 0.75 - iOS
>= 15.1 - Android
minSdk >= 24 - Android
compileSdk >= 35recommended
Dependency Model
The npm package intentionally has no regular JavaScript dependencies.
Peer dependencies:
{
"react": "*",
"react-native": ">=0.75"
}Native dependencies are declared where React Native expects them:
- Android Gradle:
react-android,androidx.core, and AndroidX Media3 ExoPlayer/session/UI modules - iOS CocoaPods:
React-Core
This is why npm can show 0 Dependencies while the native Android and iOS pieces are still installed during the normal React Native build.
Usage
import { RSPlayer } from '@rsplayer/rsplayer';
RSPlayer.addListener(event => {
console.log(event);
});
await RSPlayer.load({
uri: 'https://example.com/audio.mp3',
title: 'Story Title',
artist: 'Visionaria',
artwork: 'https://example.com/artwork.png',
autoPlay: true,
});
await RSPlayer.pause();
await RSPlayer.play();
await RSPlayer.seekTo(120);Cue Playback
Use playCue for short one-shot secondary sounds such as coaching prompts,
navigation cues, or voice instructions that should play over the main track.
Cue playback uses a separate native player, does not replace the main track, and never publishes system media controls, lock-screen metadata, or a media notification. The returned promise resolves when the cue finishes, so apps can temporarily lower the main player volume and restore it afterward.
Starting a new cue replaces the active cue. To play multiple cues in order,
await each playCue call before starting the next one.
await RSPlayer.setVolume(0.25);
try {
await RSPlayer.playCue({
uri: 'https://example.com/cues/stand-up.mp3',
volume: 1,
});
} finally {
await RSPlayer.setVolume(1);
}Call stopCue() to cancel the active cue. This resolves the active cue promise.
Managed Controller
For apps that need a reusable playback state layer, createRSPlayerController wraps the native module and translates native events into a small subscribable snapshot.
import { createRSPlayerController } from '@rsplayer/rsplayer';
type Track = {
artist?: string;
artwork?: string;
duration?: number;
title?: string;
uri: string;
};
const player = createRSPlayerController<Track>({
getLoadOptions: (track, autoPlay) => ({
artist: track.artist,
artwork: track.artwork,
autoPlay,
showSystemControls: true,
title: track.title,
uri: track.uri,
}),
getTrackDuration: track => track?.duration ?? 0,
onError: error => {
console.error(error);
},
});
const unsubscribe = player.subscribe(() => {
console.log(player.getSnapshot());
});
await player.playTrack(track);
await player.seekBy(15);
await player.pause();
unsubscribe();API
load(options)
Loads an audio URL.
type RSPlayerLoadOptions = {
uri: string;
title?: string;
artist?: string;
artwork?: string;
autoPlay?: boolean;
headers?: Record<string, string>;
loop?: boolean;
showSystemControls?: boolean;
volume?: number;
};type RSPlayerCueOptions = {
uri: string;
headers?: Record<string, string>;
volume?: number;
};showSystemControls defaults to true. Set it to false for in-app ambient audio that should not create lock-screen, notification, or remote-control media UI.
Methods
Audio methods:
RSPlayer.play()RSPlayer.pause()RSPlayer.playCue(options)RSPlayer.stop()RSPlayer.stopCue()RSPlayer.reset()RSPlayer.seekTo(seconds)RSPlayer.setLoop(loop)RSPlayer.setVolume(volume)RSPlayer.getState()RSPlayer.addListener(listener)
Events
state:idle,loading,buffering,paused,playing,endedload: initial duration/progress dataprogress: position, duration, bufferedendederror
Background Playback Setup
iOS
Add background audio capability to the app that installs this package.
In Xcode:
Target -> Signing & Capabilities -> + Capability -> Background Modes -> check Audio, AirPlay, and Picture in Picture.
Or add this to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>Android
The library manifest contributes the media playback service and base permissions:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />On Android 13+, the host app should request notification permission at runtime if it wants the media notification to be visible.
Video-Only Playback
RSVideo is an optional secondary component for muted video clips when your app
already has a separate audio session, soundtrack, voice-over, or cue system.
This component exists because a normal video player can still touch platform
audio systems even when the video is muted. On Android that can request or change
audio focus; on iOS the video item can still expose audible media tracks to the
audio session. In guided sessions this can interrupt an active RSPlayer
soundtrack or voice-over flow.
RSVideo is not a general replacement for a full video player. It intentionally
does not provide video sound, system media controls, fullscreen, background
video, or picture-in-picture. Use it for visual-only clips where audio belongs to
RSPlayer.
import { RSVideo } from '@rsplayer/rsplayer';
function ExerciseSegmentVideo({ activeSegment, isPlaying, videoControls }) {
return (
<RSVideo
key={activeSegment.id}
muted
onBuffer={videoControls.onVideoBuffer}
onError={videoControls.onVideoError}
onLoad={videoControls.onVideoLoad}
onLoadStart={videoControls.onVideoLoadStart}
paused={!isPlaying}
ref={videoControls.videoRef}
resizeMode="cover"
source={{ uri: activeSegment.playbackUrl }}
style={{ height: 240, width: '100%' }}
/>
);
}RSVideo Props
RSVideo accepts a focused visual-playback API:
type RSVideoProps = {
source: { uri: string };
paused?: boolean;
muted?: boolean;
resizeMode?: 'contain' | 'cover' | 'stretch';
style?: StyleProp<ViewStyle>;
onLoadStart?: () => void;
onLoad?: (event: {
duration: number;
naturalSize?: {
width: number;
height: number;
};
}) => void;
onBuffer?: (event: { isBuffering: boolean }) => void;
onError?: (event: {
error?: {
error?: string;
errorString?: string;
localizedDescription?: string;
localizedFailureReason?: string;
};
}) => void;
};source: required video URI, for examplesource={{ uri: clipUrl }}.paused: controls playback. Passtrueto pause andfalseto play. Default isfalse.muted: keeps video silent. Default istrue.RSVideois built for visual-only playback, so keep this enabled whenRSPlayerowns the audio.resizeMode: controls how the video fits the view. Usecoverfor full-screen timeline backgrounds,containto show the whole video, orstretchto fill without preserving aspect ratio. Default iscontain.style: React Native view style. For full-screen video, use an absolute-fill style or a parent with a fixed size.onLoadStart: fires when a new video source starts loading.onLoad: fires when metadata is ready and includesdurationin seconds plus optionalnaturalSize.onBuffer: fires with{ isBuffering }whenever native buffering state changes.onError: fires with native error details if playback fails.
Use a ref when the visual timeline must jump to a specific point:
const videoRef = useRef<RSVideoRef | null>(null);
videoRef.current?.seek(12.5);
<RSVideo
ref={videoRef}
muted
paused={!isPlaying}
resizeMode="cover"
source={{ uri: activeSegment.playbackUrl }}
style={StyleSheet.absoluteFill}
/>;Notes
- Android uses Media3 ExoPlayer and
MediaSessionService. - iOS uses
AVPlayer,AVAudioSession,MPNowPlayingInfoCenter, andMPRemoteCommandCenter. - The package handles playback only. App-level queues, mini-player UI, analytics, and saved progress should stay in your app.
