rtmp_streaming is a Flutter plugin that provides unified streaming and video recording for Android and iOS.
| Protocol | Android | iOS | Example URL |
|---|---|---|---|
| RTMP | β | β | rtmp://host/live/stream |
| RTSP | β | β | rtsp://host:8554/live |
| SRT | β | β | srt://host:10080?streamid=#!::r=live/livestream,m=publish |
| UDP | β | β | udp://host:5004 |
| WHIP | β | β (alpha) | https://host/whip |
| WHEP | β | β (alpha) | https://host/whep |
Pass an explicit StreamingProtocol (default rtmp). iOS WHIP/WHEP use RTCHaishinKit (H264/OPUS) and require Flutter SPM.
- Android: Based on
com.github.pedroSG94.RootEncoder:library:2.8.0 - iOS: Based on HaishinKit 2.2.5 (includes
SRTHaishinKit,RTCHaishinKitfor WHIP/WHEP alpha)
By leveraging these mature libraries, rtmp_streaming provides a consistent cross-platform API interface, reducing development complexity.
- No suitable Flutter RTMP plugin exists on pub.dev.
- Existing plugins suffer from:
- Long-term lack of maintenance.
- Outdated dependencies, incompatible with the latest Flutter and platform SDKs.
Therefore, the goal of rtmp_streaming is to deliver a modern, stable, and maintainable RTMP streaming solution.
- π· Get available cameras:
availableCameras - βοΈ Initialize plugin:
initialize - π¬ Prepare for streaming (optional, recommended on iOS):
prepareForVideoStreaming - π₯ Start local video recording:
startVideoRecording - βΉοΈ Stop local video recording:
stopRecording - π‘ Start recording and streaming:
startVideoRecordingAndStreaming - βΉοΈ Stop recording or streaming:
stopRecordingOrStreaming - π‘ Start video streaming:
startVideoStreaming(url,protocol,bitrate; WHIP optionalwhipToken) - βΉοΈ Stop video streaming:
stopStreaming - π Switch camera:
switchCamera - π Toggle mic capture on/off:
switchAudio - π Temporary mute while streaming:
getHasAudio/setHasAudio - π₯ Temporary video mute while streaming:
getHasVideo/setHasVideo - ποΈ Audio bitrate:
setAudioSettings - ποΈ Video encoder settings:
setVideoSettings - π¬ Frame rate:
setFrameRate - π‘ Toggle flashlight:
switchFlashLight - π Stream statistics:
getStreamStatistics - ποΈ Dispose plugin:
dispose - πΈ Snapshot while streaming:
takePicture - πΌοΈ Overlay text/image:
setOverlayText/setOverlayImage/clearOverlay
Since HaishinKit supports RTMP playback as well as publishing:
- βΈοΈ Pause stream playback:
pauseVideoStreamPlay(pauseStream)Note: pauses playback, not publishing.
βΆοΈ Resume stream playback:resumeVideoStreamPlay(resumeStream)- π± Multitasking camera:
setMultitaskingCameraAccessEnabled(HaishinKit 2.2.5+, iOS 17+ when supported) - βοΈ Session preset:
setSessionPreset - πΌοΈ Screen dimensions:
setScreenSettings - ποΈ
setVideoSettingsextras:expectedFrameRate,bitRateMode(2.2.1+ / 2.2.2+),profileLevel - π‘ Multi-streaming:
startMultiStreaming/stopStreamingDestination/stopMultiStreaming(no WHIP/WHEP)
- βΈοΈ Pause recording:
pauseVideoRecording βΆοΈ Resume recording:resumeVideoRecording- π¨ Apply filter:
setFilterβ see CameraNativeView.kt fortypevalues - β Remove filter:
removeFilter - ποΈ Pitch shift:
setPitchShift(RootEncoderPitchShiftEffect;1.0disables) - π Exposure lock:
lockExposure/unlockExposure/isExposureLocked(after preview or streaming starts) - π¨ BT.709 encoding:
setForceBt709Color(RootEncoder 2.7.0+) - πΆ RTMP ping / RTT:
setRtmpShouldSendPings(RootEncoder 2.7.0+, RTMP only)
final cameras = await availableCameras();
final controller = CameraController(
ResolutionPreset.high,
enableAudio: true,
);
await controller.initialize(cameras.first);
// iOS: pre-attach audio to reduce start latency
await controller.prepareForVideoStreaming();
await controller.setAudioSettings(128 * 1024); // bps
await controller.setVideoSettings(bitrate: 1500 * 1024);
await controller.setFrameRate(30);
if (Platform.isAndroid) {
await controller.setForceBt709Color(true);
await controller.setRtmpShouldSendPings(true);
}
if (Platform.isIOS) {
await controller.setMultitaskingCameraAccessEnabled(true);
await controller.setVideoSettings(
expectedFrameRate: 30,
bitRateMode: 'average',
);
}
await controller.startVideoStreaming(
'rtmp://your-server/live/stream-key',
protocol: StreamingProtocol.rtmp,
);
// SRT (both platforms)
// await controller.startVideoStreaming(
// 'srt://your-server:10080?streamid=#!::r=live/livestream,m=publish',
// protocol: StreamingProtocol.srt,
// );
// WHIP (Android + iOS alpha) / WHEP (iOS alpha)
// await controller.startVideoStreaming(
// 'https://your-server/whip',
// protocol: StreamingProtocol.whip,
// whipToken: 'optional-bearer-token',
// );
// await controller.startVideoStreaming(
// 'https://your-server/whep',
// protocol: StreamingProtocol.whep,
// );- Purpose: Pre-warm the capture session for streaming. On iOS, attaches audio early; on Android, no-op (safe to call for shared code).
- When: After
initialize, beforestartVideoStreaming.
| Method | Behavior | Use case |
|---|---|---|
switchAudio(false) |
Detach / re-attach mic capture | Fully stop mic input |
setHasAudio(false) |
Temporary mute while still capturing | Quick mute without teardown |
await controller.setHasAudio(false);
final sending = await controller.getHasAudio(); // false
await controller.switchAudio(false);- Purpose: Temporarily stop or resume sending video while streaming.
- Platform: Android sends black frames via OpenGL; iOS uses mixer video mute.
- When: While streaming.
await controller.setHasVideo(false);
final hasVideo = await controller.getHasVideo();
await controller.setHasVideo(true);- Purpose: AAC encoder bitrate in bps.
- When: After
initialize, before starting stream/record.
await controller.setAudioSettings(128 * 1024);
await controller.startVideoStreaming(url);| Parameter | Cross-platform | Notes |
|---|---|---|
bitrate |
β | Android can hot-update while live via setVideoBitrateOnFly. |
width / height |
Partial | Prefer before go-live. |
frameInterval |
Mostly iOS | Keyframe interval (seconds). |
profileLevel |
iOS only | H.264 profile/level string. |
expectedFrameRate |
iOS only | RTMP onMetaData framerate (2.2.2+). |
bitRateMode |
iOS only | average / constant (iOS 16+) / variable (iOS 26+). |
await controller.setVideoSettings(bitrate: 1200 * 1024);
await controller.setVideoSettings(bitrate: 800 * 1024); // hot update on Android
await controller.setVideoSettings(
expectedFrameRate: 30,
bitRateMode: 'average',
);- Purpose: Target capture/encode frame rate.
- When: After
initialize, before streaming.
await controller.setFrameRate(30);
await controller.startVideoStreaming(url);Returns StreamStatistics while streaming. Key fields:
| Field | Description |
|---|---|
bitrate, fps, width, height |
Stream metrics |
cacheSize |
Send buffer size |
sentAudioFrames / sentVideoFrames |
Android |
droppedAudioFrames / droppedVideoFrames |
Android |
isAudioMuted / isVideoMuted |
Both platforms (1.0.8+) |
rttMicros |
Android RTT (requires setRtmpShouldSendPings) |
bytesSend |
Bytes sent |
final stats = await controller.getStreamStatistics();await controller.setForceBt709Color(true);
await controller.startVideoStreaming(url);// Raise pitch (chipmunk). Pass 1.0 to disable.
await controller.setPitchShift(1.8);Works on Android and iOS. fontSize drives glyph size; scale is a % of natural size (100 = 1:1).
await controller.setOverlayText(
text: 'LIVE',
fontSize: 28,
colorArgb: 0xFFFF0000,
position: OverlayPosition.topLeft,
);
await controller.setOverlayImage(
filePath: '/path/to/logo.png',
position: OverlayPosition.bottomRight,
);
await controller.clearOverlay();iOS only. WHIP/WHEP are not allowed. Example app defaults to one RTMP + one SRT (dest1 / dest2); use Stop dest1 only to drop one path.
await controller.startMultiStreaming([
StreamDestination(
url: 'rtmp://a/live/live',
protocol: StreamingProtocol.rtmp,
id: 'a',
),
StreamDestination(
url: 'srt://a:10080?streamid=#!::r=live/livestream,m=publish',
protocol: StreamingProtocol.srt,
id: 'b',
),
]);
await controller.stopStreamingDestination('a');
await controller.stopMultiStreaming();Call after preview or streaming has started.
final locked = await controller.lockExposure();
final isLocked = await controller.isExposureLocked();
await controller.unlockExposure();await controller.setRtmpShouldSendPings(true);
await controller.startVideoStreaming(url);
final stats = await controller.getStreamStatistics();
print(stats.rttMicros);await controller.setMultitaskingCameraAccessEnabled(true);
await controller.startVideoStreaming(url);rtmp_streaming provides cross-platform RTMP streaming and recording for Flutter.
Since 1.0.8, temporary audio/video mute, encoder settings, and frame rate APIs are aligned on both platforms; iOS retains playback and multitasking extras, Android retains filters, BT.709, and RTT.