Red5 Android SDK
Build low-latency live streaming apps with the Red5 Android SDK. Stream video using whip, and subscribe (play) streams via whep. Compatible with both Red5Pro Cloud (Stream Manager) and standalone Red5Pro servers.
Overview
The SDK supports both publishing to Red5 Cloud (Stream Manager deployments) and standalone Red5Pro servers, as well as subscription (playback) for all streams on both cloud and standalone deployments.
Installation
Place the .aar SDK file in your app/libs folder.
Add dependencies to your app’s build.gradle as follows:
// Red5 SDK Dependencies
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation 'androidx.annotation:annotation:1.9.1'
implementation 'com.google.code.gson:gson:2.13.2'
implementation 'com.squareup.okhttp3:okhttp:5.1.0'
implementation 'io.github.webrtc-sdk:android:137.7151.03'
implementation 'com.pubnub:pubnub-gson:11.0.0'
Configuration Notes
Ensure you have the necessary permissions in your AndroidManifest.xml for publishing:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Requirements
- Red5Pro SDK license key
- Camera and microphone permissions for publishing
- Internet permission for both publishing and subscribing
Quick Start
Here’s a minimal example using the builder pattern:
// Publishing/subscribing to cloud.
// For standalone, use setServerIp() and setServerPort() instead of setStreamManagerHost()
IRed5WebrtcClient webrtcClient = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost(YOUR_STREAM_MANAGER_HOST_ADDRESS) //e.g. "userid-737-7f2a874662.cloud.red5.net"
.setUserName(USERNAME_IF_USERNAME_PASS_AUTH_ENABLED)
.setPassword(PASSWORD_IF_USERNAME_PASS_AUTH_ENABLED)
.setToken(AUTH_TOKEN_IF_ENABLED)
.setVideoEnabled(true)
.setAudioEnabled(true)
.setVideoWidth(1280)
.setVideoHeight(720)
.setVideoFps(30)
.setVideoBitrate(1500)
.setVideoSource(IRed5WebrtcClient.StreamSource.FRONT_CAMERA)
.setVideoRenderer(surfaceView)
.setEventListener(this)
.build();
// Publish a stream
webrtcClient.publish("myStream");
// Subscribe to a stream
webrtcClient.subscribe("myStream");
Publishing
To publish, you need to request camera/microphone permissions, create a Red5WebrtcClient, and call publish().
Step 1: Create an activity with Red5Renderer
Set up your activity and add a Red5Renderer to your layout for preview display. This is an extension of WebRTC’s SurfaceViewRenderer.
private Red5Renderer surfaceView;
<net.red5.android.core.Red5Renderer
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
Step 2: Request Publish Permissions
private static final String[] REQUIRED_PERMISSIONS = {
Manifest.permission.CAMERA,
Manifest.permission.RECORD_AUDIO
};
private void checkPermissions() {
if (hasAllPermissions()) {
createWebrtcClient();
} else {
ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, PERMISSION_REQUEST_CODE);
}
}
private boolean hasAllPermissions() {
for (String permission : REQUIRED_PERMISSIONS) {
if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
Step 3: Create Red5WebrtcClient object
Create the WebRTC client when publish permissions are granted. This single object handles all streaming configuration.
Kotlin Example:
val webrtcClient: IRed5WebrtcClient? = IRed5WebrtcClient.builder()
.setActivity(this.requireActivity())
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost("userid-737-7f2a874662.cloud.red5.net") // For cloud
// .setServerIp("192.168.1.100") // For standalone
// .setServerPort(5080) // For standalone (optional, default is 5080)
.setUserName(USERNAME_IF_USERNAME_PASS_AUTH_ENABLED)
.setPassword(PASSWORD_IF_USERNAME_PASS_AUTH_ENABLED)
.setToken(AUTH_TOKEN_IF_ENABLED)
.setVideoEnabled(true)
.setAudioEnabled(true)
.setVideoWidth(1280)
.setVideoHeight(720)
.setVideoFps(30)
.setVideoBitrate(1500)
.setVideoSource(IRed5WebrtcClient.StreamSource.FRONT_CAMERA)
.setVideoRenderer(surfaceView)
.setEventListener(this)
.build()
Java Example:
IRed5WebrtcClient webrtcClient = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost("userid-000-xxxxxxxxxx.cloud.red5.net") // For cloud
// .setServerIp("192.168.1.100") // For standalone
// .setServerPort(5080) // For standalone (optional, default is 5080)
.setUserName(USERNAME_IF_USERNAME_PASS_AUTH_ENABLED)
.setPassword(PASSWORD_IF_USERNAME_PASS_AUTH_ENABLED)
.setToken(AUTH_TOKEN_IF_ENABLED)
.setVideoEnabled(true)
.setAudioEnabled(true)
.setVideoWidth(1280)
.setVideoHeight(720)
.setVideoFps(30)
.setVideoBitrate(1500)
.setVideoSource(IRed5WebrtcClient.StreamSource.FRONT_CAMERA)
.setVideoRenderer(surfaceView)
.setEventListener(this)
.build();
Step 4: Start Preview
When webrtcClient is created, it performs a license check. Implement IRed5WebrtcClient.Red5EventListener in your activity and override onLicenseValidated.
@Override
public void onLicenseValidated(boolean validated, String message) {
if (validated) {
webrtcClient.startPreview();
Toast.makeText(this, "License check success", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "License check failed: " + message, Toast.LENGTH_SHORT).show();
}
}
Step 5: Start Publishing
Call webrtcClient.publish(YOUR_STREAM_NAME) to start publishing.
webrtcClient.publish("myStreamName");
Subscribing
Step 1: Create an activity with Red5Renderer
<net.red5.android.core.Red5Renderer
android:id="@+id/surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerInParent="true" />
Step 2: Create Red5WebrtcClient object
Configure the client for subscription. Use the same host configuration as publishing.
IRed5WebrtcClient webrtcClient = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost("userid-737-7f2a874662.cloud.red5.net") // For cloud
.setUserName(USERNAME_IF_USERNAME_PASS_AUTH_ENABLED)
.setPassword(PASSWORD_IF_USERNAME_PASS_AUTH_ENABLED)
.setToken(AUTH_TOKEN_IF_ENABLED)
.setVideoRenderer(surfaceView)
.setEventListener(this)
.build();
Step 3: Start Subscribing
webrtcClient.subscribe("myStreamName");
Listening For Events
Implement IRed5WebrtcClient.Red5EventListener in your activity to handle SDK events.
Event Types
void onPublishStarted();
void onPublishStopped();
void onPublishFailed(String error);
void onSubscribeStarted();
void onSubscribeStopped();
void onSubscribeFailed(String error);
void onIceConnectionStateChanged(IceConnectionState state);
void onConnectionStateChanged(PeerConnectionState state);
void onError(String error);
void onPreviewStarted();
void onPreviewStopped();
void onLicenseValidated(boolean validated, String message);
Connection State Handling
@Override
public void onIceConnectionStateChanged(IRed5WebrtcClient.IceConnectionState state) {
switch (state) {
case CONNECTED:
runOnUiThread(() -> {
Toast.makeText(this, "Connected to server", Toast.LENGTH_SHORT).show();
});
break;
case DISCONNECTED:
case FAILED:
runOnUiThread(() -> {
Toast.makeText(this, "Connection lost", Toast.LENGTH_SHORT).show();
});
break;
default:
break;
}
}
Advanced Usage
Turn Off/On Camera
Toggle camera on/off during streaming:
private boolean isCameraEnabled = true;
private void toggleCamera() {
isCameraEnabled = !isCameraEnabled;
webrtcClient.toggleSendVideo(isCameraEnabled);
}
Switch Camera
private void switchCamera() {
webrtcClient.switchCamera();
}
Mute/Unmute Microphone
private boolean isMicEnabled = true;
private void toggleMic() {
isMicEnabled = !isMicEnabled;
webrtcClient.toggleSendAudio(isMicEnabled);
}
Picture in Picture (PiP) Mode
Red5 SDK fully supports PiP mode for both publishing and subscribing.
Auto-enter PiP mode when user navigates away:
@Override
protected void onUserLeaveHint() {
super.onUserLeaveHint();
// Auto-enter PiP mode when user navigates away (if publishing and supported)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
isPublishing &&
!isInPictureInPictureMode()) {
enterPictureInPictureMode();
}
}
Manually enter PiP mode:
@TargetApi(Build.VERSION_CODES.O)
public void enterPictureInPictureMode() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Rational aspectRatio = new Rational(surfaceView.getWidth(), surfaceView.getHeight());
PictureInPictureParams params = new PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build();
boolean result = enterPictureInPictureMode(params);
if (!result) {
Toast.makeText(this, "Could not enter Picture-in-Picture mode", Toast.LENGTH_SHORT).show();
}
}
}
Chat Integration
The Red5 Android SDK includes built-in chat functionality using a channel-based architecture.
Chat Setup
When building your Red5WebrtcClient, include your PubNub publish and subscribe keys.
IRed5WebrtcClient webrtcClient = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setChatUserId(USER_ID)
// .setChatToken("") // Optional: Set chat token if auth is enabled
.setPubnubPublishKey(YOUR_PUBNUB_PUBLISH_KEY)
.setPubnubSubscribeKey(YOUR_PUBNUB_SUBSCRIBE_KEY)
.setEventListener(this)
.build();
Subscribe to a Channel
@Override
public void onLicenseValidated(boolean validated, String message) {
if (validated) {
String channelName = "my-chat-room";
webrtcClient.subscribeChatChannel(channelName);
}
}
Chat Operations
Send Text Messages
String channelName = "my-chat-room";
String message = "Hello, everyone!";
Object metadata = null; // Optional metadata
webrtcClient.sendChatTextMessage(channelName, message, metadata);
Send JSON Messages
JsonObject jsonMessage = new JsonObject();
jsonMessage.addProperty("text", "Hello!");
jsonMessage.addProperty("userName", "John");
jsonMessage.addProperty("timestamp", System.currentTimeMillis());
Object metadata = Map.of("sender", "John", "type", "greeting");
webrtcClient.sendChatJsonMessage(channelName, jsonMessage, metadata);
Other Operations
// Unsubscribe
webrtcClient.unsubscribeChatChannel(channelName);
// Get Subscribed Channels
List<String> channels = webrtcClient.getSubscribedChatChannels();
// Disconnect
webrtcClient.disconnectChat();
// Destroy
webrtcClient.destroyChat();
Listening for Chat Events
@Override
public void onChatConnected() {
Toast.makeText(this, "Chat connected", Toast.LENGTH_SHORT).show();
}
@Override
public void onChatDisconnected() {
Toast.makeText(this, "Chat disconnected", Toast.LENGTH_SHORT).show();
}
@Override
public void onChatMessageReceived(String channel, JsonElement message) {
if (message != null && message.isJsonObject()) {
JsonObject jsonObject = message.asJsonObject();
String text = jsonObject.get("text").getAsString();
String userName = jsonObject.get("userName").getAsString();
Log.d("Chat", "Message from " + userName + ": " + text);
}
}
@Override
public void onChatSendSuccess(String channel, Long timetoken) {
Log.d("Chat", "Message sent successfully with timetoken: " + timetoken);
}
@Override
public void onChatSendError(String channel, String errorMessage) {
Toast.makeText(this, "Failed to send message: " + errorMessage, Toast.LENGTH_SHORT).show();
}
Conferencing
The Red5 Android SDK provides infinitely scalable real-time conferencing capabilities. Note: Conferencing requires Red5 Cloud (Stream Manager) and does not work with standalone servers.
Joining a Conference Room
Step 1: Initialize Red5WebrtcClient with Conference Listener
private IRed5WebrtcClient red5Client;
private IRed5WebrtcClient.ConferenceListener conferenceListener;
private void initSdk() {
conferenceListener = new IRed5WebrtcClient.ConferenceListener() {
@Override
public void onJoinRoomSuccess(String roomId, ArrayList participants) {
Log.d(TAG, "Joined room: " + roomId + " with " + participants.size() + " participants");
}
@Override
public void onJoinRoomFailed(int statusCode, String message) {
Log.e(TAG, "Join failed: " + message);
}
@Override
public void onParticipantJoined(String uid, String role, String metaData,
boolean videoEnabled, boolean audioEnabled,
Red5Renderer renderer) {
Log.d(TAG, "Participant joined: " + uid + " (role: " + role + ")");
}
@Override
public void onParticipantLeft(String uid) {
Log.d(TAG, "Participant left: " + uid);
}
@Override
public void onParticipantMediaUpdate(String uid, boolean videoEnabled,
boolean audioEnabled, long timestamp) {
Log.d(TAG, "Participant " + uid + " - video: " + videoEnabled + ", audio: " + audioEnabled);
}
};
red5Client = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost(YOUR_STREAM_MANAGER_HOST)
.setVideoEnabled(true)
.setAudioEnabled(true)
.setVideoWidth(1280)
.setVideoHeight(720)
.setVideoFps(30)
.setVideoBitrate(1500)
.setVideoSource(IRed5WebrtcClient.StreamSource.FRONT_CAMERA)
.setVideoRenderer(localVideoRenderer)
.setEventListener(this)
.setConferenceListener(conferenceListener) // Set conference listener
.build();
}
Step 2: Join the Room
String roomId = "my-conference-room";
String userId = "john_" + System.currentTimeMillis();
String token = ""; // Optional authentication token
String role = "publisher"; // "publisher" or "subscriber"
String metaData = "{\"name\":\"John Doe\"}"; // Optional JSON metadata
red5Client.join(roomId, userId, token, role, metaData);
Leaving a Conference Room
red5Client.release();
Listening for Conference Events
onJoinRoomSuccess
Called when you successfully join a conference room.
@Override
public void onJoinRoomSuccess(String roomId, ArrayList participants) {
runOnUiThread(() -> {
Toast.makeText(this, "Joined room: " + roomId, Toast.LENGTH_SHORT).show();
// Update UI with initial participant count
updateParticipantCount(participants.size());
});
}
onParticipantJoined
Called when a new participant joins the room. This is where you receive their video renderer.
@Override
public void onParticipantJoined(String uid, String role, String metaData,
boolean videoEnabled, boolean audioEnabled,
Red5Renderer renderer) {
runOnUiThread(() -> {
// Add participant to your UI
if (renderer != null) {
// Add renderer to your layout to display participant's video
participantContainer.addView(renderer);
}
Log.d(TAG, "New participant: " + uid);
updateParticipantList();
});
}
onParticipantMediaUpdate
Called when a participant toggles their camera or microphone.
@Override
public void onParticipantMediaUpdate(String uid, boolean videoEnabled,
boolean audioEnabled, long timestamp) {
runOnUiThread(() -> {
// Update UI to show participant's media state
updateParticipantMediaState(uid, videoEnabled, audioEnabled);
if (!videoEnabled) {
showCameraOffIndicator(uid);
}
if (!audioEnabled) {
showMutedIndicator(uid);
}
});
}
Stats Collector
The Stats Collector automatically gathers WebRTC statistics every 2 seconds (configurable).
Enabling Stats Collection
IRed5WebrtcClient webrtcClient = IRed5WebrtcClient.builder()
.setActivity(this)
.setLicenseKey(YOUR_SDK_LICENSE_KEY)
.setStreamManagerHost(YOUR_STREAM_MANAGER_HOST)
// ... other config ...
// Enable stats collection by default its enabled
.setStatsCollectorEnabled(true)
// Optional: Set polling interval (default is 2000ms)
.setStatsPollingIntervalMs(2000)
.build();
Receiving Stats
Implement the onRtcStats() callback in your Red5EventListener:
@Override
public void onRtcStats(RTCStats stats) {
// Called every 2 seconds (or your configured interval) with updated stats
Log.d(TAG, "TX Bitrate: " + stats.txKBitRate + " kbps");
Log.d(TAG, "RX Bitrate: " + stats.rxKBitRate + " kbps");
Log.d(TAG, "Packet Loss: " + stats.rxPacketLossRate + "%");
}
Audio Levels
The Stats Collector provides real-time audio level monitoring for voice activity detection.
Local Audio Level
@Override
public void onRtcStats(RTCStats stats) {
// Local microphone level (0.0 = silence, 1.0 = maximum)
double micLevel = stats.localAudioLevel;
// Show visual indicator when speaking
if (micLevel > 0.05) {
showMicrophoneActivity();
} else {
hideMicrophoneActivity();
}
}
Remote Participant Audio Levels
@Override
public void onRtcStats(RTCStats stats) {
// Iterate through all remote participants
for (Map.Entry<String, RemoteParticipantStats> entry : stats.participantStats.entrySet()) {
String participantId = entry.getKey();
RemoteParticipantStats pStats = entry.getValue();
// Get participant's audio level (0.0 - 1.0)
double audioLevel = pStats.audioLevel;
// Update UI to show who is speaking
if (audioLevel > 0.05) {
highlightSpeakingParticipant(participantId);
}
}
}
Conference Stats
Access per-participant statistics in conference mode:
@Override
public void onRtcStats(RTCStats stats) {
// Iterate through each participant
for (Map.Entry<String, RemoteParticipantStats> entry : stats.participantStats.entrySet()) {
String participantUid = entry.getKey();
RemoteParticipantStats pStats = entry.getValue();
Log.d(TAG, "=== Participant: " + participantUid + " ===");
Log.d(TAG, "Audio Level: " + pStats.audioLevel);
Log.d(TAG, "RX Bitrate: " + pStats.rxKBitRate + " kbps");
Log.d(TAG, "Packet Loss: " + pStats.packetLossRate + "%");
Log.d(TAG, "RTT: " + pStats.rtt + " ms");
}
}