Try Red5 Cloud + PubNub!
Power Real-Time Interactivity at Global Scale /

Red5 Documentation

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

Download Android Archive file: https://red5-cloud-sdk.cachefly.net/index.html

Place the .aar SDK file in your app/libs folder.

Add dependencies to your app’s build.gradle as follows:

gradle
// 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:

xml
<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:

java
// 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.

java
private Red5Renderer surfaceView;
xml
<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

java
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:

kotlin
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:

java
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.

java
@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.

java
webrtcClient.publish("myStreamName");

Subscribing

Step 1: Create an activity with Red5Renderer

xml
<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.

java
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

java
webrtcClient.subscribe("myStreamName");

Listening For Events

Implement IRed5WebrtcClient.Red5EventListener in your activity to handle SDK events.

Event Types

java
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

java
@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:

java
private boolean isCameraEnabled = true;

private void toggleCamera() {
    isCameraEnabled = !isCameraEnabled;
    webrtcClient.toggleSendVideo(isCameraEnabled);
}

Switch Camera

java
private void switchCamera() {
    webrtcClient.switchCamera();
}

Mute/Unmute Microphone

java
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:

java
@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:

java
@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.

java
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

java
@Override
public void onLicenseValidated(boolean validated, String message) {
    if (validated) {
        String channelName = "my-chat-room";
        webrtcClient.subscribeChatChannel(channelName);
    }
}

Chat Operations

Send Text Messages

java
String channelName = "my-chat-room";
String message = "Hello, everyone!";
Object metadata = null; // Optional metadata

webrtcClient.sendChatTextMessage(channelName, message, metadata);

Send JSON Messages

java
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

java
// Unsubscribe
webrtcClient.unsubscribeChatChannel(channelName);

// Get Subscribed Channels
List<String> channels = webrtcClient.getSubscribedChatChannels();

// Disconnect
webrtcClient.disconnectChat();

// Destroy
webrtcClient.destroyChat();

Listening for Chat Events

java
@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

java
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

java
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

java
red5Client.release();

Listening for Conference Events

onJoinRoomSuccess

Called when you successfully join a conference room.

java
@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.

java
@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.

java
@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

java
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:

java
@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

java
@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

java
@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:

java
@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");
    }
}