MOQ Beta | Now Open For Developers / Learn More

Red5 Documentation

Android SDK

Build low-latency live streaming apps with the Red5 Android SDK. See the API Reference for a complete list of classes, interfaces, and methods. Stream video using WHIP, and subscribe (play) streams via WHEP. Compatible with both Red5 Pro Cloud (Stream Manager) and standalone Red5 Pro servers.

Overview

The SDK supports both publishing to Red5 Cloud (Stream Manager deployments) and standalone Red5 Pro 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:

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

  • Red5 Pro 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();
        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(() -> {
        if (renderer != null) {
            participantContainer.addView(renderer);
        }
        updateParticipantList();
    });
}

onParticipantMediaUpdate

Called when a participant toggles their camera or microphone.

@Override
public void onParticipantMediaUpdate(String uid, boolean videoEnabled,
                                    boolean audioEnabled, long timestamp) {
    runOnUiThread(() -> {
        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 ...
    .setStatsCollectorEnabled(true)
    .setStatsPollingIntervalMs(2000) // Optional: default is 2000ms
    .build();

Receiving Stats

Implement the onRtcStats() callback in your Red5EventListener:

@Override
public void onRtcStats(RTCStats 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;

    if (micLevel > 0.05) {
        showMicrophoneActivity();
    } else {
        hideMicrophoneActivity();
    }
}

Remote Participant Audio Levels

@Override
public void onRtcStats(RTCStats stats) {
    for (Map.Entry<String, RemoteParticipantStats> entry : stats.participantStats.entrySet()) {
        String participantId = entry.getKey();
        RemoteParticipantStats pStats = entry.getValue();

        double audioLevel = pStats.audioLevel;

        if (audioLevel > 0.05) {
            highlightSpeakingParticipant(participantId);
        }
    }
}

Conference Stats

Access per-participant statistics in conference mode:

@Override
public void onRtcStats(RTCStats stats) {
    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");
    }
}