NAV Navbar
Phenix logo
Version 2022.0.9
Java

Android SDK - Low Level APIs

The following low level APIs are provided in the Android sdk:

Android SDK

Initializing

Before using a PCast object, it is required to be initialized as shown.

import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.PCastInitializeOptions;
import com.phenixrts.pcast.android.AndroidPCastFactory;

PCast pcast = AndroidPCastFactory.createPCast(this);

// #1 Default initialization options
pcast.initialize();

// OR

// #2 Custom initialization options (initialized by constructor)
final boolean enableProcessTerminationSignalHandling = false;
PCastInitializeOptions initOptions = new PCastInitializeOptions(
    enableProcessTerminationSignalHandling);

pcast.initialize(initOptions);

Initialize Options

Name Description
enableProcessTerminationSignalHandling Controls if process termination signal handling is enabled or not
configureLogging Control if phenix logging is enabled
streamingSourceMapping Allows partial override of streaming source URIs

Stream Source Mapping

Optional field that can be provided with PCastInitializeOptions. It only has an effect on the subscriber side, and only for streams with the “streaming” capability (on both publisher and subscriber side). The property allows you to redirect requests from the SDKs player to your own CDN.

import com.phenixrts.pcast.PCastInitializeOptions;
import com.phenixrts.pcast.StreamingSourceMapping;

final String patternToReplace = "https:\\/\\/phenixrts\\.com\\/video";
final String replacement = "https://myown.cdn.com";
final StreamingSourceMapping streamingSourceMapping = new StreamingSourceMapping(
    patternToReplace, replacement);

final boolean enableProcessTerminationSignalHandling = false;
final boolean configureLogging = true;

final PCastInitializeOptions pcastInitOptions = new PCastInitializeOptions(
    enableProcessTerminationSignalHandling,
    configureLogging,
    streamingSourceMapping);
Name Description
patternToReplace A regular expression indicating with part of the incoming streaming source URI to replace
replacement The replacement string to insert into the streaming source URI

Note: The regular expression has to be properly escaped to work correctly.

Connect and Authenticate

Please follow the self explanatory example for connecting and authenticating to PCast.

import android.util.Log;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.RequestStatus;

// Obtained via Admin API
String authenticationToken = ...;
// Previously initialized
PCast pcast = ...;

pcast.start(
    authenticationToken,
    new PCast.AuthenticationCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus requestStatus,
            String sessionId) {
                if (requestStatus == RequestStatus.OK) {
                    Log.i("Phenix SDK Example", "PCast started...");
                } else {
                    Log.e("Phenix SDK Example", "Failed to start PCast...");
                }
        }
    },
    new PCast.OnlineCallback() {
        @Override
        public void onEvent(PCast pcast) {
            Log.i("Phenix SDK Example", "We are online...");
        }
    },
    new PCast.OfflineCallback() {
        @Override
        public void onEvent(PCast pcast) {
            Log.i("Phenix SDK Example", "We are offline...");
        }
    });

Parameters

Name Type Description
authenticationToken (required) string The authentication token generated using the Admin API
authenticationCallback (required) PCast.AuthenticationCallback Called upon successful authentication or when authentication failed or has to be redone. Upon successful authentication, the authenticationCallback will be called with status=RequestStatus.OK. If at any time a new authenticationToken is required, then the authenticationCallback is called with status=RequestStatus.UNAUTHORIZED to indicate that we are no longer authenticated.
onlineCallback (required) PCast.OnlineCallback Called when the client is connected to the streaming platform
offlineCallback (required) PCast.OfflineCallback Called when the client is disconnected from the streaming platform. Ongoing streams may continue while we are temporarily disconnected. However, no new streams can be started while being disconnected. The client automatically tries to reconnect and will call onlineCallback when it succeeds in doing so or eventually call authenticationCallback to indicate that re-authentication is required.

Authentication Callback Status Codes

Status Valid Fields Description
RequestStatus.OK sessionId Authentication succeeded, the sessionId is populated
RequestStatus.UNAUTHORIZED <none> Authentication failed or re-authentication required
<varies> <none> Authentication failed for other reasons

Disconnect

Please follow the self explanatory example for disconnecting from PCast.

import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.RequestStatus;

// Previously initialized and started
PCast pcast = ...;

pcast.stop();

// Once you are done using PCast (e.g. exiting the app)
pcast.shutdown();

Get Local User Media

Please follow the self explanatory example for getting local user media.

import com.phenixrts.pcast.FacingMode;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.UserMediaStream;
import com.phenixrts.pcast.android.AndroidPCastFactory;

// Previously initialized and started
PCast pcast = ...;

UserMediaOptions gumOptions = new UserMediaOptions();

// Customize options if desired
gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FACING_MODE, Arrays.asList(new DeviceConstraint(FacingMode.USER)));
gumOptions.getAudioOptions().enabled = false;

pcast.getUserMedia(
    gumOptions,
    new PCast.UserMediaCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus status,
            UserMediaStream userMediaStream) {
                // Check status and store 'userMediaStream'
        }
    });

Parameters

Name Type Description
options (required) UserMediaOptions The options defining the requested user media stream
userMediaCallback (required) PCast.UserMediaCallback Upon acquiring of the user media stream, the userMediaCallback will be called with status=RequestStatus.OK. If the user media is currently used by another application, then you may receive a code status=RequestStatus.CONFLICT . If the operation fails with status=RequestStatus.FAILED then please check the logs for more information

Device Capability

Name Description
DeviceCapability.WIDTH Width in pixels
DeviceCapability.HEIGHT Height in pixels
DeviceCapability.FRAME_RATE Number of frames per second
DeviceCapability.FACING_MODE Facing mode
DeviceCapability.FLASH_MODE Flash mode
DeviceCapability.DEVICE_ID Device ID string (obtain from here)
DeviceCapability.LOCATION Device Location
DeviceCapability.POLAR_PATTERN Polar pattern
DeviceCapability.AUDIO_ECHO_CANCELATION_MODE Audio echo cancelation mode

Constraint Type

Name Description
ConstraintType.MIN Hard constraint: Capability must have at least the specified value
ConstraintType.MAX Hard constraint: Capability must have at most the specified value
ConstraintType.EXACT Hard constraint: Capability must have exactly the specified value
ConstraintType.IDEAL Soft constraint: Capability should have specified value, but other values are acceptable (default)

Facing Mode

Name Description
FacingMode.AUTOMATIC Select a facing mode automatically (default)
FacingMode.ENVIRONMENT Facing the surrounding environment (e.g., back camera)
FacingMode.USER Facing the user (e.g., front camera)

Flash Mode

Only applicable to video devices

Name Description
FlashMode.AUTOMATIC Flash is turned on automatically when needed (default)
FlashMode.ALWAYS_ON Flash is on (if available)
FlashMode.ALWAYS_OFF Flash is off

Device Location

Name Description
Location.AUTOMATIC Select any device (default)
Location.UPPER Mounted on top of phone/tablet
Location.LOWER Mounted at bottom of phone/tablet

Polar Pattern

Only applicable to audio devices

Name Description
PolarPattern.AUTOMATIC Automatically select pattern (default)
PolarPattern.OMNIDIRECTIONAL Equally sensitive to sound from any direction
PolarPattern.CARDIOID Most sensitive to sound from the direction in which the data source points and is (nearly) insensitive to sound from the opposite direction
PolarPattern.SUBCARDIOID Most sensitive to sound from the direction in which the data source points and is less sensitive to sound from the opposite direction

Note: This option is currently not supported on Android

Audio Echo Cancelation Mode

Only applicable to audio devices

Name Description
AudioEchoCancelationMode.AUTOMATIC Automatically select AEC mode (default)
AudioEchoCancelationMode.ON Enable AEC if available
AudioEchoCancelationMode.OFF Disable AEC

Note (as of v2019.2.0): Automatic mode is currently always set to “off” on Android.

Note 2 (as of v2019.2.0): AudioEchoCancelationMode must be set to ON in the UserMediaOptions for all published streams and RendererOptions for all subscribed streams in order for AEC to work reliably on all Android devices.

Updating Options

Sometimes you find it useful to change the camera while a stream is running or just would like to turn on the flash light temporarily.

import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.UserMediaStream;

// Previously obtained via 'getUserMedia'
UserMediaStream userMediaStream = ...;

// Previously initialized and used with 'getUserMedia'
UserMediaOptions gumOptions = new UserMediaOptions();

gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FACING_MODE, Arrays.asList(new DeviceConstraint(FacingMode.ENVIRONMENT)));
gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FLASH_MODE, Arrays.asList(new DeviceConstraint(FlashMode.ALWAYS_ON)));
gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.HEIGHT,
    Arrays.asList(new DeviceConstraint(720, ConstraintType.EXACT)));
gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.WIDTH,
    Arrays.asList(
        new DeviceConstraint(800, ConstraintType.MIN),
        new DeviceConstraint(1500, ConstraintType.MAX)));

userMediaStream.applyOptions(gumOptions);

Enumerating Source Devices

You can get a list of available source devices.

import android.util.Log;
import com.phenixrts.pcast.MediaType;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.SourceDeviceInfo;

// Previously initialized and started
PCast pcast = ...;

pcast.enumerateSourceDevices(
    MediaType.VIDEO,
    new PCast.EnumerateSourceDevicesCallback() {
        @Override
        public void onEvent(PCast pcast, SourceDeviceInfo[] devices) {
            // Store devices as needed
        }
    });

Parameters

Name Type Description
mediaType (required) MediaType The media type for which to enumerate source devices

SourceDeviceInfo fields

Name Type Description
id String Source device ID
name String Source device Name
mediaType MediaType Source device media type
deviceType SourceDeviceType Source device type
facingMode FacingMode Source device facing mode

Media Type

Name Description
MediaType.VIDEO Video
MediaType.AUDIO Audio

Source Device Type

Name Description
SourceDeviceType.NULL Null device (e.g. blank screen or silence)
SourceDeviceType.PHYSICAL Physical device (e.g. camera or microphone)
SourceDeviceType.SYSTEM_OUTPUT System output capture (screencast)

Screencasting android 5.0 or later

You can also stream the screen of your phone.

In order to do this, you must first enumerate the available source devices to find the screencast device ID.

Find the screencast device ID

import android.util.Log;
import com.phenixrts.pcast.MediaType;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.SourceDeviceInfo;
import com.phenixrts.pcast.SourceDeviceType;

// Previously initialized and started
PCast pcast = ...;

pcast.enumerateSourceDevices(
    MediaType.VIDEO,
    new PCast.EnumerateSourceDevicesCallback() {
        @Override
        public void onEvent(PCast pcast, SourceDeviceInfo[] devices) {
            for (SourceDeviceInfo info : devices) {
                if (info.sourceDeviceType == SourceDeviceType.SYSTEM_OUTPUT) {
                    Log.i("Phenix SDK Example", "Screencasting is available");
                    String screenCaptureDeviceId = info.id;
                    // Store screenCaptureDeviceId
                }
            }
        }
    });

pcast.enumerateSourceDevices();

The screen capture device requires a valid android.media.projection.MediaProjection object to be passed to the SDK before it can be used. The MediaProjection must stay valid for the duration of the screencast.

Next, pass a valid android.media.projection.MediaProjection object

import android.media.projection.MediaProjection;
import com.phenixrts.pcast.android.AndroidPCastFactory;

// Previously obtained by calling
// android.media.projection.MediaProjectionManager.getMediaProjection(),
// see Android API reference.
MediaProjection mediaProjection = ...;

AndroidPCastFactory.setMediaProjection(mediaProjection);

Finally, pass the screen capture device ID

Next, pass the screen capture device ID to getUserMedia() in the user media options.

import com.phenixrts.pcast.FacingMode;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.UserMediaStream;

// Previously initialized and started
PCast pcast = ...;

// Found by calling enumerateSourceDevices() as described above
String screenCaptureDeviceId = ...;

UserMediaOptions gumOptions = new UserMediaOptions();

gumOptions.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.DEVICE_ID, Arrays.asList(new DeviceConstraint(screenCaptureDeviceId)));

pcast.getUserMedia(
    gumOptions,
    new PCast.UserMediaCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus status,
            UserMediaStream userMediaStream) {
                // Check status and store 'userMediaStream'
        }
    });

Please follow the self explanatory code snippets for understanding above steps.

Publish a Stream

import android.util.Log;
import com.phenixrts.pcast.MediaStream;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.Publisher;
import com.phenixrts.pcast.RequestStatus;
import com.phenixrts.pcast.StreamEndedReason;
import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.UserMediaStream;

// Previously initialized and started
PCast pcast = ...;
// Previously obtained via Admin API
String streamToken = ...;
// Previously obtained via either PCast.subscribe()
// or UserMediaStream.getMediaStream()
MediaStream mediaStream = ...;

String[] tags = { "my-tag" };

pcast.publish(
    streamToken,
    mediaStream,
    new PCast.PublishCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus requestStatus,
            Publisher publisher) {
                // Check status and store 'publisher'

                // The "streamId" of the publisher
                String streamId = publisher.getStreamId();

                if (publisher.hasEnded()) {
                    // Checks if the publisher has ended
                }

                // Attach publisher ended callback
                publisher.setPublisherEndedCallback(
                    new Publisher.PublisherEndedCallback() {
                        @Override
                        public void onEvent(
                            Publisher publisher,
                            StreamEndedReason reason,
                            String reasonDescription) {
                                // Called when the stream has ended
                                Log.i("Phenix SDK Example",
                                    "Publish stream ended with reason ["
                                    + reasonDescription + "]");
                        }
                    });

                // To stop later
                publisher.stop("I-am-done-publishing");
        }
    },
    tags);

Parameters

Name Type Description
streamToken (required) String The publish token is generated using the Admin API
mediaStream (required) MediaStream The user media stream acquired through PCast.subscribe(…) or locally with UserMediaStream.getMediaStream()
publishCallback (required) PCast.PublishCallback Called upon completion of the operation
tags (optional) String[] Tags that will be provided with the stream notifications to your backend callback endpoint

StreamEndedReason

Reason Description
StreamEndedReason.ENDED The stream ended normally
StreamEndedReason.FAILED The stream failed
StreamEndedReason.CENSORED The stream was censored
StreamEndedReason.MAINTENANCE A maintenance event caused this stream to be terminated
StreamEndedReason.CAPACITY The stream was terminated due to capacity limitations
StreamEndedReason.APP_BACKGROUND The stream was terminated due to the mobile app entering into the background
StreamEndedReason.CUSTOM A custom termination reason is provided in the “reasonDescription” field

Process Raw Frames

The frame-ready API allows for pre-processing of raw audio and video frames before they are encoded and transmitted.

Any processing needs to keep up with the incoming frame rate; otherwise some of the incoming frames will be dropped to compensate.

import android.graphics.Bitmap;
import com.phenixrts.media.audio.android.AndroidAudioFrame;
import com.phenixrts.media.video.android.AndroidVideoFrame;
import com.phenixrts.pcast.FrameNotification;
import com.phenixrts.pcast.MediaStreamTrack;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.UserMediaStream;
import com.phenixrts.pcast.android.AndroidReadAudioFrameCallback;
import com.phenixrts.pcast.android.AndroidReadVideoFrameCallback;

// Previously obtained via PCast.getUserMedia
final UserMediaStream userMediaStream = ...;

final MediaStreamTrack[] videoTracks = userMediaStream.getMediaStream().getVideoTracks();

for (MediaStreamTrack videoTrack : videoTracks) {
  userMediaStream.setFrameReadyCallback(videoTrack,
      (frameNotification) -> {
        frameNotification.read(new AndroidReadVideoFrameCallback() {
          @Override
          public void onVideoFrameEvent(AndroidVideoFrame videoFrame) {
            final Bitmap pixelBuffer = videoFrame.bitmap;
            final int displayHeightInPixels = pixelBuffer.getHeight();
            final int displayWidthInPixels = pixelBuffer.getWidth();

            // Manipulate bitmap as needed
            ...

            frameNotification.write(videoFrame);
          }
        });
      });
}

final MediaStreamTrack[] audioTracks = userMediaStream.getMediaStream().getAudioTracks();

for (MediaStreamTrack audioTrack : audioTracks) {
  userMediaStream.setFrameReadyCallback(audioTrack,
      (frameNotification) -> {
        frameNotification.read(new AndroidReadAudioFrameCallback() {
          @Override
          public void onAudioFrameEvent(AndroidAudioFrame audioFrame) {
            // Attenuate audio:
            for (int sampleIndex = 0; sampleIndex < audioFrame.audioSamples.length; ++sampleIndex) {
              audioFrame.audioSamples[sampleIndex] *= 0.1;
            }

            frameNotification.write(audioFrame);
          }
        });
      });
}

Frame Notification API

Name Argument Description
read FrameReadyForProcessingCallback Retrieves current raw frame in form of an AndroidAudioFrame or AndroidVideoFrame
write AndroidAudioFrame/AndroidVideoFrame Writes back a processed or newly generated frame. The frame can have a different resolution and timestamps
drop (none) Instructs stream to drop the current frame

AndroidAudioFrame

Name Type Description
sampleRateInHz int Audio sampling frequency, e.g. 48000
numberOfChannels int Number of channels, e.g. 1 for mono, 2 for stereo
timestampInMicroseconds long Timestamp of current audio frame in us
audioSamples short[] Audio samples; if more than one channel is present, then samples will be interleaved

AndroidVideoFrame

Name Type Description
bitmap android.graphics.Bitmap Bitmap containing the pixel data. This object also contains other properties such as width and height.
timestampInMicroseconds long Timestamp of current video frame in us
durationInMicroseconds long Duration of the current video frame in us

Limit Bitrate

The published video bitrate can be limited temporarily if needed. The returned disposable allows you to control for how long the limitation should stay in effect. If limitBandwidth is called multiple times before any of the previous disposables are released, then only the most recent override will remain in effect until its disposable is released. Relasing any of the disposables from earlier limitBandwidth calls will have no effect.

To release a Disposable deterministrically, call dispose on it.

import com.phenixrts.common.Disposable;
import com.phenixrts.pcast.Publisher;

// Previously obtained
Publisher publisher = ...;

Disposable disposable = publisher.limitBandwidth(200000);

// ... after some time: force dispose to cancel bandwidth limitation:
disposable.dispose();

Parameters

Name Type Description
bandwidthLimitInBps (required) long Maximum bitrate limit in bps for video

Subscribe to a Stream

import android.util.Log;
import com.phenixrts.pcast.MediaStream;
import com.phenixrts.pcast.PCast;
import com.phenixrts.pcast.RequestStatus;
import com.phenixrts.pcast.StreamEndedReason;

// Previously initialized and started
PCast pcast = ...;
// Previously obtained via Admin API
String streamToken = ...;

pcast.subscribe(
    streamToken,
    new PCast.SubscribeCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus requestStatus,
            MediaStream mediaStream) {
                // Check status and store 'mediaStream'

                // Attach stream ended callback
                mediaStream.setStreamEndedCallback(
                    new MediaStream.StreamEndedCallback() {
                        @Override
                        public void onEvent(
                            MediaStream mediaStream,
                            StreamEndedReason reason,
                            String reasonDescription) {
                                Log.i("Phenix SDK Example",
                                    "Subscriber stream ended with reason ["
                                    + reasonDescription + "]");
                        }
                    }
                });

                // To stop later
                mediaStream.stop();
        }
    });

Parameters

Name Type Description
streamToken (required) String The publish token is generated using the Admin API
subscribeCallback (required) PCast.SubsribeCallback Called upon completion of the operation

View a Stream

In order to view a stream you have to supply a Surface for the Renderer to draw on.

import com.phenixrts.pcast.MediaStream;
import com.phenixrts.pcast.Renderer;
import com.phenixrts.pcast.RendererStartStatus;
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;

// Previously obtained via either PCast.subscribe
// or PhenixUserMediaStream.mediaStream
MediaStream mediaStream = ...;

// Previously obtained, e.g., from a SurfaceView
Surface renderSurface = ...;

Renderer renderer = mediaStream.createRenderer();

RendererStartStatus status =
    renderer.start(new AndroidVideoRenderSurface(renderSurface));

if (status == RendererStartStatus.OK) {
    Log.i("Phenix SDK Example", "Renderer started successfully");
} else {
    Log.e("Phenix SDK Example", "Renderer start failed");
}

// To stop later
renderer.stop();

Renderer options

It is possible to pass additional options when creating a renderer.

import com.phenixrts.pcast.AspectRatioMode;
import com.phenixrts.pcast.MediaStream;
import com.phenixrts.pcast.Renderer;
import com.phenixrts.pcast.RendererOptions;

// Previously obtained via either PCast.subscribe or UserMediaStream.mediaStream
MediaStream mediaStream = ...;

RendererOptions options = new RendererOptions();
options.aspectRatioMode = AspectRatioMode.FILL;

Renderer renderer = mediaStream.createRenderer(options);

Properties

Name Type Description
aspectRatioMode (optional) AspectRatioMode How to fill available video render surface
audioEchoCancelationMode (optional) AudioEchoCancelationMode Audio echo cancelation mode
hardwareAcceleratedDecodingMode (optional) HardwareAcceleratedDecodingMode Hardware accelerated decoding mode

Aspect Ratio Mode

Name Description
AspectRatioMode.AUTOMATIC Defaults to fill
AspectRatioMode.FILL Fill entire render area. Video may be truncated
AspectRatioMode.LETTERBOX Black bars are added on sides or top/bottom of render area, video will not be truncated

Note: This option is currently only supported for Real-Time streams

Hardware Accelerated Decoding Mode

Name Description
HardwareAcceleratedDecodingMode.AUTOMATIC Use hardware decoding on certified devices
HardwareAcceleratedDecodingMode.ON Always use hardware decoding
HardwareAcceleratedDecodingMode.OFF Always use software decoding

Preview Local User Media

import com.phenixrts.pcast.Renderer;
import com.phenixrts.pcast.UserMediaStream;

// Previously obtained via 'getUserMedia'
UserMediaStream userMediaStream = ...;

Renderer renderer = userMediaStream.getMediaStream().createRenderer();

Muting and Unmuting of Audio

import com.phenixrts.pcast.Renderer;

// Previously obtained from media stream
Renderer renderer = ...;

boolean isMuted = renderer.isAudioMuted();
renderer.muteAudio();
renderer.unmuteAudio();

Taking a Screenshot

If you would like to show a preview, you can take a still image from a renderer.

import com.phenixrts.pcast.Renderer;
import com.phenixrts.android.AndroidLastFrameRenderedCallback;

// Previously obtained from media stream and started
Renderer renderer = ...;

renderer.setLastVideoFrameRenderedReceivedCallback(
    new AndroidLastFrameRenderedCallback() {
        @override
        public void onEvent(Renderer renderer, Bitmap videoFrame) {
            // Process the frame as needed
        }
    });

renderer.requestLastVideoFrameRendered();

You can also take a still image from a local stream.

import com.phenixrts.pcast.UserMediaStream;
import com.phenixrts.android.AndroidLastFrameCapturedCallback;

// Previously obtained via 'getUserMedia'
UserMediaStream userMediaStream = ...;

userMediaStream.setLastVideoFrameCapturedReceivedCallback(
    new AndroidLastFrameCapturedCallback() {
        @override
        public void onEvent(Renderer renderer, Bitmap videoFrame) {
            // Process the frame as needed
        }
    });

userMediaStream.requestLastVideoFrameCaptured();

Data Quality Feedback

If you like to show the user feedback about how their internet connectivity affects the stream quality, you can listen for data quality notifications.

import com.phenixrts.pcast.DataQualityReason;
import com.phenixrts.pcast.DataQualityStatus;
import com.phenixrts.pcast.Publisher;
import com.phenixrts.pcast.Renderer;

// Previously obtained from media stream and started
Renderer renderer = ...;

renderer.setDataQualityChangedCallback(
    new Renderer.DataQualityChangedCallback() {
        @Override
        public void onEvent(
            Renderer renderer,
            DataQualityStatus quality,
            DataQualityReason reason) {
                // Inform user, take action based on status and reason
        }
    });

// Previously obtained via PCast.publish
Publisher publisher = ...;

publisher.setDataQualityChangedCallback(
    new Publisher.DataQualityChangedCallback() {
        @Override
        public void onEvent(
            Publisher publisher,
            DataQualityStatus quality,
            DataQualityReason reason) {
                // Inform user, take action based on status and reason
        }
    });

Data Quality Status for Publishers

Status Reason Description
DataQualityStatus.NO_DATA DataQualityReason.NONE The publisher has a bad internet connection and no data is being streamed.
DataQualityStatus.NO_DATA DataQualityReason.UPLOAD_LIMITED The publisher has a bad internet connection and no data is being streamed.
DataQualityStatus.ALL DataQualityReason.NONE Good internet connection and no quality reduction in effect.
DataQualityStatus.ALL DataQualityReason.UPLOAD_LIMITED The publisher has a slow internet connection and the quality of the stream is reduced.
DataQualityStatus.ALL DataQualityReason.NETWORK_LIMITED Subscribers have bad internet connections and the quality of the stream is reduced.
DataQualityStatus.AUDIO_ONLY DataQualityReason.UPLOAD_LIMITED The publisher has a bad internet connection and only audio is streamed.
DataQualityStatus.AUDIO_ONLY DataQualityReason.NETWORK_LIMITED Subscribers have bad internet connections and only audio is streamed.

Data Quality Status for Viewers

Status Reason Description
DataQualityStatus.NO_DATA DataQualityReason.NONE The subscriber has a bad internet connection and no data is being received.
DataQualityStatus.NO_DATA DataQualityReason.DOWNLOAD_LIMITED The subscriber has a bad internet connection and no data is being received.
DataQualityStatus.NO_DATA DataQualityReason.PUBLISHER_LIMITED The publisher has a bad internet connection and no data is being received.
DataQualityStatus.NO_DATA DataQualityReason.NETWORK_LIMITED The network is limiting the quality of the stream and no data is being received.
DataQualityStatus.ALL DataQualityReason.NONE Good internet connection and no quality reduction in effect.
DataQualityStatus.ALL DataQualityReason.DOWNLOAD_LIMITED The subscriber has a bad internet connection and the quality of the stream is reduced.
DataQualityStatus.ALL DataQualityReason.PUBLISHER_LIMITED The publisher has a bad internet connection and the quality of the stream is reduced.
DataQualityStatus.ALL DataQualityReason.NETWORK_LIMITED Other subscribers have bad internet connections and the quality of the stream is reduced.
DataQualityStatus.AUDIO_ONLY DataQualityReason.NONE Audio only stream, good internet connection and no quality reduction in effect.
DataQualityStatus.AUDIO_ONLY DataQualityReason.DOWNLOAD_LIMITED The subscriber has a bad internet connection and is only receiving audio.
DataQualityStatus.AUDIO_ONLY DataQualityReason.PUBLISHER_LIMITED The publisher has a bad internet connection and the subscriber is only receiving audio.
DataQualityStatus.AUDIO_ONLY DataQualityReason.NETWORK_LIMITED The network is limiting the quality of the stream and the subscriber is only receiving audio.

Handling Dimension Changes

Cameras may be switched at runtime and devices may be rotated. Register a handler to receive a notification whenever the video dimension changes.

import com.phenixrts.pcast.Dimensions;
import com.phenixrts.pcast.Renderer;

// Previously obtained from media stream
Renderer renderer = ...;

renderer.setVideoDisplayDimensionsChangedCallback(
    new Renderer.VideoDisplayDimensionsChangedCallback() {
        @Override
        public void onEvent(
            Renderer renderer,
            Dimensions displayDimensions) {
                // Can get called multiple times while rendering a stream
                // Values in displayDimensions.width, displayDimensions.height
        }
    });

Room Service

Set up a room and manage members, streams, and chat. Some typical uses for rooms include:

To get started, create a room of the type associated with your need.

Initializing

First you will need to initialize a room service object. This object is used for creating, joining, and leaving a room. As well, use this object for managing the room you enter (active room), members, and the model of your Self.

Note: PCast must be initialized and started when invoking createRoomService so that a valud session ID is present.

import android.content.Context;

import com.phenixrts.environment.android.AndroidContext;
import com.phenixrts.pcast.PCast;
import com.phenixrts.room.RoomService;
import com.phenixrts.room.RoomServiceFactory;

// IMPORTANT: Before accessing any of the static factories, make sure the context is passed to Phenix:
final Context context = ...;  // e.g. Activity.getApplication();
AndroidContext.setContext(context);

final PCast pcast = ...;  // previously obtained
final RoomService roomService = RoomServiceFactory.createRoomService(pcast);

Parameters

Name Type Description
pcast (required) PCast Instantiated PCast object. Must already be authenticated

RoomService

Name Signature Returns Description
getRoomInfo (roomId, alias, callback) Room See Get Room Info
createRoom (room, callback) Room See Create a Room
joinRoom (roomId, alias, callback) Room See Join a Room
leaveRoom (callback) void See Leave a Room
destroyRoom (callback) void See Destroy a Room
getSelf () Observable of Member) Observable of self member
getObservableActiveRoom () Observable of Room) Observable of the active room

Instantiate Self Member Model

Before entering a room the RoomService must have a valid model of the Self member. The Self member model may be updated at any time by changing observable values and calling commitChanges. See Update Self Model.

Update Self Model

Broadcast local changes of the Self model to all the members in the room. These changes are sent to our server which forwards the changes to all members in the room. This works whether or not you are currently in a room.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Member;
import com.phenixrts.room.MemberRole;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained
final Member selfMember = roomService.getSelf();

selfMember.getObservableScreenName().setValue("My New Screen Name");
selfMember.getObservableRole().setValue(MemberRole.AUDIENCE);

// Commit changes to self if already in a room
selfMember.commitChanges((RequestStatus status, String message) -> {
  if (status == RequestStatus.OK) {
    // Successfully updated self
  } else {
    // Handle error
  }
});

To undo uncommitted local changes, use reload:

import com.phenixrts.room.Member;
import com.phenixrts.room.MemberRole;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained
final Member selfMember = roomService.getSelf();

selfMember.getObservableScreenName().setValue("My New Screen Name");
selfMember.getObservableRole().setValue(MemberRole.AUDIENCE);

// Decided to undo changes
selfMember.reload();

Update Self Callback Arguments

Name Type Description
status RequestStatus The status of the operation
message String Error message, if applicable

Set Self Streams

The self model must have at least one stream when joining the room as any member role besides Audience.

import com.phenixrts.room.Member;
import com.phenixrts.room.RoomService;
import com.phenixrts.room.Stream;
import com.phenixrts.room.StreamType;
import com.phenixrts.room.TrackState;

final RoomService roomService = ...;  // previously obtained
final Member selfMember = roomService.getSelf();

final Stream selfStream = roomService.createStream(
    "http://myuri.stream", StreamType.USER, TrackState.ENABLED, TrackState.ENABLED);

selfMember.getObservableStreams().setValue(new Stream[] {selfStream});

// Now commit changes

Parameters

Name Type Description
uri (required) String Unique identifier for stream.
type (required) StreamType Type of stream - see Stream Types
audioState (required) TrackState State of the audio track - see Stream Track States
videoState (required) TrackState State of the video track - see Stream Track States

Get Room Info

Get room info with the provided room details without joining the room.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

// Either provide alias or roomId, other can be null or empty
final String roomId = "us-central#demo#exampleRoomId";
final String alias = null;

roomService.getRoomInfo(roomId, alias, (RoomService roomService, RequestStatus status, Room room) -> {
  if (status == RequestStatus.OK) {
    // Do something with room
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
roomId (required) String ID of the room
alias (optional) String Alternative to roomId - alias of the room
getRoomInfoCallback (required) RoomService.GetRoomInfoCallback Callback with the status of the request and the room model

Get Room Info Callback Arguments

Name Type Description
roomService RoomService The room service making the callback
status RequestStatus The status of the operation
room Room Immutable room object

Get Room Info Callback Status Codes

Status Description
ok Get room info succeeded
not-found Get room info failed. Room does not exist - verify room data or create the room
<varies> Get room info failed for other reasons

Create a Room

Create a room with the provided details

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomOptions;
import com.phenixrts.room.RoomService;
import com.phenixrts.room.RoomServiceFactory;
import com.phenixrts.room.RoomType;

final RoomService roomService = ...;  // previously obtained

final String roomName = "My Room Name";
final String description = "Deep thoughts only";

final RoomOptions roomOptions = RoomServiceFactory.createRoomOptionsBuilder()
    .withName(roomName)
    .withDescription(description)
    .withType(RoomType.MULTI_PARTY_CHAT)
    .buildRoomOptions();

roomService.createRoom(
    roomOptions,
    (RoomService service, RequestStatus status, Room room) -> {
      if (status == RequestStatus.OK) {
        // Do something with room
      } else {
        // Handle error
      }
    });

Parameters

Name Type Description
options (required) RoomOptions Room creation options
callback (required) RoomService.CreateRoomCallback Callback with the status of the request and the room model

Create Room Callback Arguments

Name Type Description
roomService RoomService The room service making the callback
status RequestStatus The status of the operation
room Room Immutable room object

Create Room Callback Status Codes

Status Description
ok Create room succeeded. This can also mean that the room already existed
<varies> Create room failed for other reasons

RoomOptionsBuilder

Name Type Default Description
withName (required) String Name of room
withType (required) RoomType Room type
withAlias (optional) String <generated> Room Alias. If not passed in, it will be generated for you
withDescription (optional) String <empty> Room description
buildRoomOptions <none> Builds the RoomOptions

Join a Room

Join a room with the Self model. After successfully joining a room that room becomes the active room and can be returned via the getObservableActiveRoom method.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

// Either provide alias or roomId, other can be null or empty
final String roomId = null;
final String alias = "exampleRoom";

roomService.joinRoom(roomId, alias, (RoomService roomService, RequestStatus status, Room room) -> {
  if (status == RequestStatus.OK) {
    // Do something with room
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
roomId (required) String ID of the room
alias (optional) String Alternative to roomId - alias of the room
callback (required) RoomService.JoinRoomCallback Callback with the status of the request and the room model

Join Room Callback Arguments

Name Type Description
roomService RoomService The room service making the callback
status RequestStatus The status of the operation
room Room Immutable room object

Join Room Callback Status Codes

Status Description
ok Join room succeeded
not-found Join room failed. Room does not exist - create room first
<varies> Join room failed for other reasons

Subscribe to Room Member Streams

After entering a room you may use the active room to get the observable members and associated metadata. Here we provide an example for accessing each member’s streams whenever the list of members is updated. Remember: Since each observable behaves like a Behavior Rx Subject, your subscriber will always get called back immediately with the current value (in this case the list of members), see Observable.

import com.phenixrts.common.Disposable;
import com.phenixrts.common.Observable;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Member;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;
import com.phenixrts.room.Stream;

final RoomService roomService = ...;  // previously obtained

final Observable<Room> observableActiveRoom = roomService.getObservableActiveRoom();

// For this simple example, we assume that there is an active room.
// In a production environment, this can return null if we have not currently joined a room. In that case
// you would want to listen as a subscriber on the 'observableActiveRoom' for updates
final Room activeRoom = observableActiveRoom.getValue();
final Observable<Member[]> observableMembers = activeRoom.getObservableMembers();

final Disposable subscription = observableMembers.subscribe((change) -> {
  final Member[] currentMembers = (Member[])change;
  for (Member member : currentMembers) {
    final Stream[] currentStreams = member.getObservableStreams().getValue();
    // Now do something with this member's streams
  }
});

// The subscription will remain active for as long as 'subscription' is kept alive or not disposed

Update Room Model

Broadcast local changes of the Room model to all the members in the room. These changes are sent to our server which forwards the changes to all members in the room.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

final Room room = roomService.getObservableActiveRoom().getValue();

room.getObservableDescription().setValue("My New Room Description");
room.commitChanges((RequestStatus status, String message) -> {
  if (status == RequestStatus.OK) {
    // Successfully updated room
  } else {
    // Handle error (`message` may provide more information about what went wrong)
  }
});

Parameters

Name Type Description
callback (required) Room.CommitCallback Callback with the status of the request

Update Room Callback Arguments

Name Type Description
status RequestStatus The status of the operation
message String Error message, if applicable

Update Room Callback Status Codes

Status Description
ok Update room succeeded
<varies> Update room failed for other reasons

Leave a Room

Leave the current active room. Self is not modified.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

roomService.leaveRoom((RoomService roomService, RequestStatus status) -> {
  if (status == RequestStatus.OK) {
    // Successfully left room, no action required
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
callback (required) RoomService.LeaveRoomCallback Callback with the status of the request

Leave Room Callback Arguments

Name Type Description
roomService RoomService The room service making the callback
status RequestStatus The status of the operation

Update Room Callback Status Codes

Status Description
ok Leave room succeeded
<varies> Leave room failed for other reasons

Destroy a Room

Destroys the current active room assuming your self member has the permissions.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.room.Room;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

roomService.destroyRoom((RoomService roomService, RequestStatus status) -> {
  if (status == RequestStatus.OK) {
    // Successfully destroyed room, no action required
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
callback (required) RoomService.DestroyRoomCallback Callback with the status of the request

Leave Room Callback Arguments

Name Type Description
roomService RoomService The room service making the callback
status RequestStatus The status of the operation

Update Room Callback Status Codes

Status Description
ok Destroy room succeeded
<varies> Destroy room failed for other reasons

Observe Room size

Subscribe to observable to receive periodic room size updates. The room size corresponds to the total number of members currently in the room, which does include audience members.

This is a so called cold observable, which means that you may not immediately receive an update. It also means that both getValue and setValue methods will cause a failure.

Updates will be provided for as long as the disposable reference is kept alive.

import com.phenixrts.common.Disposable;
import com.phenixrts.room.ImmutableRoom;

final ImmutableRoom room = ...  // previously obtained

final Disposable disposable = room.getObservableEstimatedSize().subscribe((change) -> {
  final Long currentEstimatedRoomSize = (Long) change;
  // E.g. update UI
});

Room Model

The following details technical specifications for the Room, Members, and Streams.

ImmutableRoom

Name Signature Returns Description
getRoomId () String The room ID
getObservableAlias () Observable(of String) Observable of room alias
getObservableName () Observable(of String) Observable of room name
getObservableDescription () Observable(of String) Observable of room description
getObservableType () Observable(of RoomType) Observable of room type
getObservableMembers () Observable(of Member[]) Observable of array of visible members in the room
getObservableBridgeId () Observable(of String) Observable of room bridgeId
getObservablePin () Observable(of String) Observable of room pin
getObservableEstimatedSize () Observable(of Long) Observable of estimated room size. This is a cumulative count of all members in the room, including audience members

Room

Room extends ImmutableRoom

Name Signature Returns Description
commitChanges (Room.CommitCallback) void Attempts to update server room model with any changes made locally
reload () void Reverts all local changes to the server room model

Room Types

Type Description
RoomType.DIRECT_CHAT One to one chat
RoomType.MULTI_PARTY_CHAT Many to many or few to many chat
RoomType.MODERATED_CHAT Moderated chat with integrated SIP Bridge for conferencing abilities (join by phone).
RoomType.TOWN_HALL Chat with a few active participants or presenters and many audience or inactive participants
RoomType.CHANNEL View only room with a single active member at a time. Broadcast content to many viewers.

Member

Name Signature Returns Description
getSessionId () String The session ID of the member in the room
getObservableRole () Observable(of MemberRole) Observable of member role
getObservableState () Observable(of MemberState) Observable of member state
getObservableScreenName () Observable(of String) Observable of member screen name
getObservableStreams () Observable(of Stream[]) Observable of array of streams belonging to the member
getObservableLastUpdate () Observable(of Date) Observable of utc timestamp
commitChanges (Member.CommitCallback) void Attempts to update server member model with any changes made locally
reload () void Reverts all local changes to the server member model

Member Roles

Role Description
MemberRole.PARTICIPANT Member with streams and is visible to all members
MemberRole.AUDIENCE Member with no streams and is not visible to other members
MemberRole.PRESENTER Presenter participant member
MemberRole.MODERATOR Privileged participant member

Member States

Role Description
MemberState.ACTIVE Member is active
MemberState.PASSIVE Member is not active
MemberState.HAND_RAISED Member is requesting to become active
MemberState.INACTIVE Member is not away
MemberState.OFFLINE Member has left the room

Stream

The stream object may be used to represent any value that will be shared between all members.

Name Signature Returns Description
getUri () String The stream uri (may or may not be associated with phenix stream ID)
getType () StreamType The stream type
getObservableAudioState () Observable(of TrackState) Observable of stream video state
getObservableVideoState () Observable(of TrackState) Observable of stream audio state

Stream Types

Type Description
StreamType.USER General purpose stream
StreamType.PRESENTATION Privileged stream
StreamType.AUDIO Audio-only stream

Stream Track States

State Description
TrackState.ENABLED Track is enabled
TrackState.DISABLED Track is disabled
TrackState.ENDED Track has ended

Chat Service

The Chat Service provides the ability to:

All room members that are subscribed to the chat will receive the text messages. You must first enter the room before you may send and receive messages.

Initializing

First, instantiate the chat service. A Room Service is required to obtain the chat service.

import android.content.Context;

import com.phenixrts.chat.RoomChatService;
import com.phenixrts.chat.RoomChatServiceFactory;
import com.phenixrts.environment.android.AndroidContext;
import com.phenixrts.room.RoomService;

// IMPORTANT: Before accessing any of the static factories, make sure the context is passed to Phenix:
final Context context = ...;  // e.g. Activity.getApplication();
AndroidContext.setContext(context);

final RoomService roomService = ...;  // previously obtained

final RoomChatService chatService = RoomChatServiceFactory.createRoomChatService(roomService);


// Use a custom batch size, i.e. max number of message returned by 'getObservableChatMessages'
final int batchSize = 10;
final RoomChatService chatServiceWithBatchSize = RoomChatServiceFactory.createRoomChatService(
    roomService, batchSize);

RoomChatService

Name Signature Returns Description
getObservableChatMessages () Observable(of ChatMessage[]) Get the observable of the most recent chat messages. This is a buffer of the most recent N messages up to a max of 100. See Listen for Chat Messages
getObservableChatEnabled () Observable(of Boolean) Get the observable of the enabled status of the chat service. Use this to disable or enable the chat.
sendMessageToRoom (message, callback) void Send a message to room.
getMessages (batchSize, afterMessageId, beforeMessageId, callback) void Get chat message history

ChatMessage

Name Type Description
getMessageId String Unique ID of message
getObservableTimeStamp Observable(of Date) Server UTC timestamp of message
getObservableFrom Observable(of ChatUser) Information on the member that sent the message
getObservableMessage Observable(of String) Chat message

ChatUser

Name Type Description
getSessionId String Member’s session ID
getObservableScreenName Observable(of String) Screen name of the member
getObservableMemberRole Observable(of MemberRole) Member’s role. See Member Roles
getObservableLastUpdate Observable(of Date) Last time member was updated as UTC timestamp

Listen for Chat Messages in the Active Room

The chat service exposes an observable of the most recent N (batchSize) messages in the active room up to a max of 100 messages. To view older messages you will need to use the getMessages method.

import com.phenixrts.common.Disposable;
import com.phenixrts.common.Observable;
import com.phenixrts.chat.ChatMessage;
import com.phenixrts.chat.RoomChatService;
import com.phenixrts.chat.RoomChatServiceFactory;
import com.phenixrts.room.RoomService;

final RoomService roomService = ...;  // previously obtained

final int batchSize = 10;
final RoomChatService chatService = RoomChatServiceFactory.createRoomChatService(roomService, batchSize);

final Observable<ChatMessage[]> chatMessageObservable = chatService.getObservableChatMessages();

final Disposable subscription = chatMessageObservable.subscribe((change) -> {
  final ChatMessage[] messages = (ChatMessage[])change;
  // Do something with chat messages
});

Send a Message to the Active Room

Send a single message to the active room. If the message is successfully sent the chatMessageObservable will trigger notifications on all subscribers with the new chat message.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.chat.ChatMessage;
import com.phenixrts.chat.RoomChatService;

final RoomChatService chatService = ...;  // previously obtained

// Without callback:
chatService.sendMessageToRoom("My First Message");

// With callback
chatService.sendMessageToRoom("My First Message", (RequestStatus status, String message) -> {
  if (status == RequestStatus.OK) {
    // Successfully sent
  } else {
    // handle error - send message again
    // (`message` may provide more information about what went wrong)
  }
});

Parameters

Name Type Description
message (required) String Message to send to the room
sendMessageCallback (optional) RoomChatService.SendMessageCallback Callback with the status of the request

Send Message Callback Arguments

Name Type Description
status RequestStatus The status of the operation
message String Optional additional error status in case of a failure

Send Message Callback Status Codes

Status Description
ok Send message succeeded
<varies> Send message failed for other reasons

Chat Message History

Get messages older than the most recent N buffered in the chatMessageObservable.

Get the 10 most recent messages after a message:

import com.phenixrts.common.RequestStatus;
import com.phenixrts.chat.ChatMessage;
import com.phenixrts.chat.RoomChatService;

final RoomChatService chatService = ...;  // previously obtained

final int batchSize = 10;
final String afterMessageId = "MessageId1";
final String beforeMessageId = null;

chatService.getMessages(
    batchSize,
    afterMessageId,
    beforeMessageId,
    (RoomChatService chatService, ChatMessage[] messages, RequestStatus status) -> {
  if (status == RequestStatus.OK) {
    // Successfully got chat messages
  } else {
    // handle error - request messages again
  }
});

Get all messages between two messages:

import com.phenixrts.common.RequestStatus;
import com.phenixrts.chat.ChatMessage;
import com.phenixrts.chat.RoomChatService;

final RoomChatService chatService = ...;  // previously obtained

final int batchSize = 100;
final String afterMessageId = "MessageId1";
final String beforeMessageId = "MessageId2";

chatService.getMessages(
    batchSize,
    afterMessageId,
    beforeMessageId,
    (RoomChatService chatService, ChatMessage[] messages, RequestStatus status) -> {
  if (status == RequestStatus.OK) {
    // Successfully got chat messages
  } else {
    // handle error - request messages again
  }
});

Parameters

Name Type Description
batchSize (required) int Limit of messages to receive.
afterMessageId (optional) String The messageId to return after. The N messages after message with ID of afterMessageId but before beforeMessageId
beforeMessageId (optional) String The messageId to return before. The N messages before message with ID of beforeMessageId
callback (required) RoomChatService.GetMessagesCallback Callback with the status of the request

Get Messages Callback Arguments

Name Type Description
roomChatService RoomChatService The chat service making the callback
messages ChatMessage[] Array of chat messages matching search criteria
status RequestStatus The status of the operation

Get Messages Callback Status Codes

Status Description
ok Get messages succeeded
<varies> Get messages failed for other reasons

Observable

Observe value changes. Similar to the ReactiveX Behavior Subject.

Important differences for Phenix SDK observables:

  1. There is always an initial value (which can be null for some objects)
  2. Only if observed value changes will subscribers get notified

Point 1 means that when subscribing, you will always get notified immediately with the current value. This is guaranteed atomic, meaning that you do not have to worry about missing any observable updates.

Observable

Name Signature Returns Description
subscribe (onChanged) Disposable See Subscribe to an Observable
getValue () varies See Get Observable Value
setValue (value) void See Set Observable Value

Subscribe to an Observable

Receive a notification every time the observable value changes.

import com.phenixrts.common.Disposable;
import com.phenixrts.common.Observable;

Observable<String> observable = ...;  // previously obtained

Disposable subscription = observable.subscribe((change) -> {
  String newStringValue = (String)change;
  // Do something with newStringValue
});

// Closing subscription will cancel it:
subscription.dispose();

Parameters

Name Type Description
onChangedHandler (required) Observable.OnChangedHandler Handler to call when value changes

Disposable

Has no methods, but does inherit from JavaObject. This allows canceling of subscription by calling dispose.

Get Observable Value

Synchronous method allowing retrieval of currently set value. Thread safe.

import com.phenixrts.common.Observable;
import com.phenixrts.room.Member;
import com.phenixrts.room.MemberRole;

Observable<MemberRole> roleObservable = ...;  // previously obtained
MemberRole role = roleObservable.getValue();

Observable<Member[]> membersObservable = ...;  // previously obtained
Member[] members = membersObservable.getValue();

Set Observable Value

Change the observable value. This will trigger notifications on all subscribers.

import com.phenixrts.common.Observable;
import com.phenixrts.room.MemberRole;

Observable<MemberRole> roleObservable = ...;  // previously obtained
roleObservable.setValue(MemberRole.PRESENTER);