@epicnephrin/react-native-rtmp-publisher
v0.1.1
Published
iOS-only React Native New Architecture RTMP publisher optimized for Mux Live ingest.
Maintainers
Readme
@epicnephrin/react-native-rtmp-publisher
@epicnephrin/react-native-rtmp-publisher is an iOS-only React Native RTMP/RTMPS publisher for Mux Live ingest. It uses native camera and microphone capture, VideoToolbox H.264 encoding, AAC-LC audio, FLV packaging, and RTMP/RTMPS transport.
Requirements
- iOS only
- React Native New Architecture only
- Expo prebuild/custom dev client or bare React Native iOS app
- Expo Go is not supported
Features
- Native local camera preview via
RtmpPublisherView - Singleton TurboModule session API
- H.264 hardware video encoding with VideoToolbox
- AAC-LC audio encoding
- FLV muxing and RTMP/RTMPS publishing
- Camera switching, torch, zoom, focus, mute, bitrate control
- Reconnect logic and runtime stats
- Orientation-aware publishing:
portrait,landscapeLeft,landscapeRight - Expo config plugin for camera/microphone permissions and iOS pod integration
Installation
npm install @epicnephrin/react-native-rtmp-publisherIf you use Expo, add the config plugin and enable the New Architecture:
{
"expo": {
"newArchEnabled": true,
"plugins": ["@epicnephrin/react-native-rtmp-publisher"]
}
}The config plugin is expected to handle iOS permission setup and the native registration glue needed for Expo prebuild apps. Consumer apps should not need manual Podfile edits or patch-package.
Current status for Expo SDK 55 / React Native 0.83:
- consumer apps should use only the package plus the Expo config plugin
- the package still applies a package-owned iOS registration workaround during prebuild
- the package plugin also injects the iOS pod for this library on this stack because Expo autolinking is not reliably discovering it
- this is temporary and exists because current Expo/RN iOS codegen does not reliably generate the required provider registration for this library
- the package also ships vendored iOS codegen artifacts on this stack, and the podspec exposes the vendored codegen include root required by those generated sources
- this is not a consumer-owned patch and should not require manual native edits in the app
Then generate and build iOS native code:
npx expo prebuild --platform ios
npx expo run:iosUse a custom dev client or production build. Expo Go is unsupported.
Important:
- native code changes require a fresh native rebuild
- a JS reload alone is only enough for JS-only changes
Mux Integration
Your backend should create the Mux live stream and return a publish URL or stream key to the broadcaster. The app should publish to:
rtmps://global-live.mux.com:443/app/<stream_key>Do not call the Mux API directly from the device and do not embed long-lived Mux secrets in the app.
Quick Start
import React, { useEffect, useState } from 'react';
import { Button, View } from 'react-native';
import {
MUX_PORTRAIT_720P,
NativeRtmpPublisher,
RtmpPublisherView,
type CameraPosition,
type PublisherConfig,
} from '@epicnephrin/react-native-rtmp-publisher';
const PUBLISH_URL = 'rtmps://global-live.mux.com:443/app/<stream_key>';
export default function LiveScreen() {
const [cameraPosition, setCameraPosition] = useState<CameraPosition>('front');
useEffect(() => {
const config: PublisherConfig = {
...MUX_PORTRAIT_720P,
video: {
...MUX_PORTRAIT_720P.video,
orientation: 'portrait',
},
camera: {
...MUX_PORTRAIT_720P.camera,
position: cameraPosition,
mirrorPreview: cameraPosition === 'front',
},
};
void NativeRtmpPublisher.prepare(config);
return () => {
void NativeRtmpPublisher.destroy();
};
}, [cameraPosition]);
async function startLive() {
await NativeRtmpPublisher.startPublish(PUBLISH_URL);
}
async function stopLive() {
await NativeRtmpPublisher.stopPublish();
}
async function switchCamera() {
await NativeRtmpPublisher.switchCamera();
setCameraPosition((current) => (current === 'front' ? 'back' : 'front'));
}
return (
<View style={{ flex: 1 }}>
<RtmpPublisherView
style={{ flex: 1 }}
cameraPosition={cameraPosition}
mirrorPreview={cameraPosition === 'front'}
resizeMode="cover"
/>
<Button title="Go live" onPress={startLive} />
<Button title="Switch camera" onPress={switchCamera} />
<Button title="Stop" onPress={stopLive} />
</View>
);
}Orientation
Set orientation in config.video.orientation:
portraitlandscapeLeftlandscapeRight
Notes:
- the selected orientation is fixed for the prepared session
- change orientation by stopping or destroying, then calling
prepare()again with a new config - the local preview is intended to follow the selected session orientation
Preset
The package currently ships:
MUX_PORTRAIT_720P
It is a portrait-oriented Mux preset with:
720x128030fps1.8 Mbpsvideo bitrate128 kbpsmono AAC audio
TurboModule API
prepare(config: PublisherConfig): Promise<void>startPublish(url: string): Promise<void>stopPublish(): Promise<void>destroy(): Promise<void>switchCamera(): Promise<void>setMuted(muted: boolean): Promise<void>setTorch(enabled: boolean): Promise<void>setZoom(zoom: number): Promise<void>focus(point: { x: number; y: number }): Promise<void>setVideoBitrate(bitrate: number): Promise<void>requestKeyFrame(): Promise<void>getStats(): Promise<PublisherStats>getDebugSnapshot(): Promise<PublisherDebugSnapshot>
Events
onStateChangeonErroronStatsonDiagnosticonPublishStartedonPublishStoppedonCameraChangedonNetworkHealth
Example:
const statsSub = NativeRtmpPublisher.addEventListener('onStats', (stats) => {
console.log('publisher stats', stats);
});
const errorSub = NativeRtmpPublisher.addEventListener('onError', (error) => {
console.error('publisher error', error);
});
const diagnosticSub = NativeRtmpPublisher.addEventListener(
'onDiagnostic',
(event) => {
console.log('publisher diagnostic', event);
},
);
// cleanup
statsSub.remove();
errorSub.remove();
diagnosticSub.remove();Types
Important public types:
PublisherConfigPublisherStatsPublisherDebugSnapshotPublisherErrorPublisherOrientationCameraPositionResizeMode
PublisherStats and PublisherDebugSnapshot expose detailed runtime fields such as:
- current publisher state
- startup stage
- transport (
rtmp/rtmps) - orientation
- bytes sent
- video packet counters
- encoded frame counters
- RTMP status details
getDebugSnapshot() is useful when runtime event delivery is unavailable or incomplete in a host app.
Expo Notes
- Use a real iPhone for end-to-end camera and microphone validation
- If you change native Swift/ObjC code, plugins, podspecs, or codegen surfaces, rebuild the native app
- If you only change JS/TS code, a Metro reload is usually enough
- On Expo SDK 55 / React Native 0.83, the package plugin currently injects the iOS pod and patches generated iOS registration files during prebuild so the TurboModule and Fabric view are registered correctly
- On this same stack, the package still relies on vendored/generated iOS codegen artifacts that are compiled from the pod target
- The intended consumer contract is still zero manual native setup: no app-side Podfile edits and no
patch-package - Pure autolinking/codegen without that package-owned workaround is not yet claimed for this Expo/RN line
Publishing To npm
The package is configured for a public scoped npm release:
- package name:
@epicnephrin/react-native-rtmp-publisher - first publish uses public access automatically via
publishConfig.access
Recommended release flow:
npm version patch
npm publishNotes:
prepublishOnlyrunsnpm run build, so the published tarball includes fresh compiled output- if this is the first publish for the
@epicnephrinscope, the npm account you use must own that scope or have publish rights to it - if
0.1.0is already published, bump to the next version before publishing
Constraints
- single publisher session only
- iOS only
- New Architecture only
- Expo Go unsupported
Development
npm install
npm run typecheck
npm test