NAV Navbar
Phenix logo
Version 2020.0.25
Java

Android SDK

The Android SDK APIs are split into two main categories: Express and Low-Level. We recommend that you start with the Express API as this:

  1. Simplifies integration
  2. Automatically recovers from stream failures
  3. Handles edge cases such as network reconnects

For more information see the Express API

If you find that the Express API doesn’t work for you, take a look at our Lower Level API;

Setup

The Phenix SDK is provided as a standard Android library module in form of an .aar file. Please check our private Github repository for details.

Extended feature support

(Optional) For extended live streaming support (Dash), you also need to add ExoPlayer as a dependency to your project:

Min SDK Requirements

Depending on required features, the minimum supported Android API levels are as follows:

Known issues

Build crash: java.io.IOException: Failed to find byte code for …

Android Studio issue: Google Issue 76403146

Solution:

  1. Upgrade : gradle-wrapper.properties - gradle-4.6-all.zip or later
  2. Upgrade gradle plugin in project build.gradle - ‘com.android.tools.build:gradle:3.2.+’ or later

Or Disable instant run - Android Studio -> Settings -> Search Instant run -> Disable

IDE error: .GroovyFileImpl because: different providers: SingleRootFileViewProvider

Android Studio issue: Google Issue 77939622

Solution:

If directory for module was created (e.g. /phenix-sdk), just add direcory name to settings.gradle file.

include ':app', ':phenix-sdk', ':phenix-utils', ':phenix-ui'

Debugging

You can retrieve the Phenix SDK logs programmatically using the collectLogMessages PCast API.

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

// Previously initialized
PCast pcast = ...;

pcast.collectLogMessages(
    new PCast.LogMessageCollectionCallback() {
        @Override
        public void onEvent(
            PCast pcast,
            RequestStatus requestStatus,
            String messages) {
          if (requestStatus != RequestStatus.OK) {
            // Handle error
            return;
          }
          String messagesArray[]= messages.split("\n");
          for (String message: messagesArray) {
            // Do something with 'message'
          }
        }
    });

Notes:

General

All Phenix SDK objects implement java.io.Closeable. To force release resources held on to by any object, just call close.

Note: Once an object has been closed, it should no longer be accessed or else it will throw an IllegalStateException

App Store

Google supports a split Android Bundle format as the replacement for the old APK format. We highly recommend using this new format, as it significantly minimizes user app download size without any additional configuration.

If you are using the APK app format, you can optimize your app by deploying multiple APKs, each specific to an ABI (i.e. processor architecture) by updating your Gradle configuration file.

// build.gradle
android {
  ...
  splits {

    // Configures multiple APKs based on ABI.
    abi {

      // Enables building multiple APKs per ABI.
      enable true

      // Resets the list of ABIs that Gradle should create APKs for to none.
      reset()

      // Specifies a list of ABIs that Gradle should create APKs for.
      include "armeabi-v7a", "arm64-v8a", "x86", "x86_64"

      // Specifies that we do not want to also generate a universal APK that includes all ABIs.
      universalApk false
    }
  }
}

With this approach, the end user will automatically download the correct version for their device when installing the app from the Play Store. Their downloaded APK will only have the Phenix SDK version that their architecture requires, making the download size smaller.

In general, when using multiple APKs, you will want to make sure that they conform to the requirements described in the Google Android Developer documentation

Express Android SDK

Our Express APIs provide a simple, single-step API for the easiest integration of streaming into your application

Common Use Cases

Channel Express

Single-step configuration based API for setting up a channel - a one-size-fits-all solution for all your one-to-many streaming applications. The Channel Express extends the lower-level Room Service API to provide easy solutions to:

Initializing

import android.content.Context;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.environment.android.AndroidContext;
import com.phenixrts.express.ChannelExpressFactory;
import com.phenixrts.express.ChannelExpressOptions;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PCastExpressOptions;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.express.RoomExpressOptions;

// 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 PCastExpressOptions pcastExpressOptions =
    PCastExpressFactory.createPCastExpressOptionsBuilder()
        .withBackendUri("https://example.yourdomain.com/phenix/")
        .withUnrecoverableErrorCallback((status, description) -> {
          // Best option is to try rebuilding the ChannelExpress instance and/or quit
          // your app
        })
        .buildPCastExpressOptions();

final RoomExpressOptions roomExpressOptions =
    RoomExpressFactory.createRoomExpressOptionsBuilder()
        .withPCastExpressOptions(pcastExpressOptions)
        .buildRoomExpressOptions();

final ChannelExpressOptions channelExpressOptions =
    ChannelExpressFactory.createChannelExpressOptionsBuilder()
        .withRoomExpressOptions(roomExpressOptions)
        .buildChannelExpressOptions();

final ChannelExpress channelExpress = ChannelExpressFactory.createChannelExpress(channelExpressOptions);

ChannelExpressOptionsBuilder

Name Type Default Description
withRoomExpressOptions (required) RoomExpressOptions See RoomExpressOptionsBuilder
buildChannelExpressOptions <none> Builds the ChannelExpressOptions

View a Channel

Join a channel and automatically view the most recent content published to that channel.

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ChannelExpress;
import com.phenixrts.express.ChannelExpressFactory;
import com.phenixrts.express.JoinChannelOptions;
import com.phenixrts.express.JoinRoomOptions;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.pcast.RendererOptions;
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;
import com.phenixrts.room.RoomService;

final ChannelExpress channelExpress = ...;  // previously obtained
final SurfaceView view = ...;  // previously obtained

final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

// Just an example (you can omit renderer options if defaults are ok)
final RendererOptions rendererOptions = new RendererOptions();
rendererOptions.aspectRatioMode = AspectRatioMode.LETTERBOX;

final JoinRoomOptions joinRoomOptions = RoomExpressFactory.createJoinRoomOptionsBuilder()
    .withRoomId("us-central#xxxxx#channel")
    .withCapabilities(new String[] { "real-time" })
    .buildJoinRoomOptions();
final JoinChannelOptions joinChannelOptions =
    ChannelExpressFactory.createJoinChannelOptionsBuilder()
        .withJoinRoomOptions(joinRoomOptions)
        .withRenderer(renderSurface)
        .withRendererOptions(rendererOptions)
        .buildJoinChannelOptions();

this.channelExpress.joinChannel(
    joinChannelOptions,
    (RequestStatus status, RoomService roomService) -> {
      if (status != RequestStatus.OK) {
        // Handle room join error
        return;
      }

      // Important: Store room service reference, otherwise we will leave channel again
      // as soon as this RoomService instance is garbage collected:
      OuterClass.this.currentRoomService = roomService;
    },
    (RequestStatus status, ExpressSubscriber subscriber, Renderer renderer) -> {
      switch (status) {
        case OK:
          // Successfully subscribed to a stream. No need to hold on to any references
          break;

        case NO_STREAM_PLAYING:
          // No stream playing in channel, update UI accordingy
          break;

        default:
          // We failed to subscribe and retry attempts must have failed
          break;
      }
    });

Parameters

Name Type Description
options (required) Options Options to join channel with
joinChannelCallback (required) ChannelExpress.JoinChannelCallback Function to call on success/failure of joining the channel.
subscriberCallback (required) PCastExpress.SubscribeCallback Function to call on when the most recent presenter changes.

JoinChannelOptionsBuilder

Name Type Default Description
withJoinRoomOptions (required) JoinRoomOptions See JoinRoomOptionsBuilder.
withRenderer (optional) AndroidVideoRenderSurface Render layer on which to display stream. If none of the withRenderer... methods are called, no renderer will be instantiated.
withRenderer (optional) Will trigger instantiation of renderer. Useful for audio only type streams that do not require a render surface.
withRendererOptions (optional) RendererOptions Options passed to renderer. Will trigger instantiation of renderer.
withStreamSelectionStrategy (optional) StreamSelectionStrategy StreamSelectionStrategy.MOST_RECENT Determines how member streams are selected for subscriptions.
buildJoinChannelOptions Builds the JoinChannelOptions

Stream Selection Strategy

Strategy Description
StreamSelectionStrategy.MOST_RECENT Select the most recent stream. Viewing stream changes any time a stream starts or is updated in the room.
StreamSelectionStrategy.HIGH_AVAILABILITY Select streams for increased reliability and redundancy. Viewing stream will only change in the event of a failure of the prior selected stream.

Express Join Channel Callback Arguments

Name Type Description
status PhenixRequestStatus The status of the operation
roomService RoomService Room service object

View Channel Subscriber Callback Status Codes

Status Description
ok Successfully subscribed to presenter
no-stream-playing No presenter in room to subscribe to. Wait for presenter to join.
<varies> Subscribe to presenter failed for other reasons

Publish to a Channel

Publish a local or remote media to a channel. Users that are viewing the channel will see your media.

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ChannelExpress;
import com.phenixrts.express.ChannelExpressFactory;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PublishOptions;
import com.phenixrts.express.PublishToChannelOptions;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.pcast.DeviceCapability;
import com.phenixrts.pcast.FacingMode;
import com.phenixrts.pcast.RendererOptions;
import com.phenixrts.pcast.UserMediaOptions
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;
import com.phenixrts.room.ChannelOptions;
import com.phenixrts.room.RoomServiceFactory;

final ChannelExpress channelExpress = ...;  // previously obtained
final SurfaceView view = ...;  // previously obtained

final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

// Using ChannelOptions means that the channel may or may not already exist.
// If the channel ID is known in advance, it is recommended to use `withChannelId` instead
// of `withChannelOptions` when assembling the `PublishToChannelOptions` below
final ChannelOptions channelOptions = RoomServiceFactory.createChannelOptionsBuilder()
    .withName("MyAwesomeChannel")
    // Not required but if it is provided we will use this as the alias instead
    // of pre-generating one for you:
    .withAlias("MyAwesomeChannelAlias")
    .buildChannelOptions();

// Example constraints. Audio and video are enabled by default
final UserMediaOptions mediaConstraints = new UserMediaOptions();
mediaConstraints.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FACING_MODE, Arrays.asList(new DeviceConstraint(FacingMode.USER)));
mediaConstraints.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FRAME_RATE, Arrays.asList(new DeviceConstraint(15)));
mediaConstraints.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.HEIGHT, Arrays.asList(new DeviceConstraint(720)));
mediaConstraints.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.WIDTH, Arrays.asList(new DeviceConstraint(1280)));

final PublishOptions publishOptions = PCastExpressFactory.createPublishOptionsBuilder()
    .withCapabilities(new String[]{"hd", "streaming"})
    .withMediaConstraints(mediaConstraints)
    .withPreviewRenderer(renderSurface)
    .buildPublishOptions();

final PublishToChannelOptions publishToChannelOptions =
    ChannelExpressFactory.createPublishToChannelOptionsBuilder()
        .withChannelOptions(channelOptions)
        .withPublishOptions(publishOptions)
        .buildPublishToChannelOptions();

channelExpress.publishToChannel(
    publishToChannelOptions,
    (publishStatus, roomService, publisher, previewRenderer) -> {
      if (publishStatus != RequestStatus.OK) {
        // Handle channel publish error
        return;
      }

      // Important: Store publisher reference, otherwise we will stop publishing again immediately:
      currentPublisher = publisher;
    }
);

// OR (without a preview):

channelExpress.publishToChannel(
    publishToChannelOptions,
    (publishStatus, roomService, publisher) -> {
      if (publishStatus != RequestStatus.OK) {
        // Handle channel publish error
        return;
      }

      // Important: Store publisher reference, otherwise we will stop publishing again immediately:
      currentPublisher = publisher;
    }
);

Parameters

Name Type Description
options (required) Options Options to publish to channel with
publisherCallback (required) Function Function to call on success/failure of publishing to the channel

PublishToChannelOptionsBuilder

Name Type Default Description
withChannelOptions (required) ChannelOptions See ChannelOptionsBuilder. If omitted, then withChannelId needs to be provided.
withChannelId (required) String ID of channel to publish to. If omitted, then withChannelOptions needs to be provided.
withPublishOptions (required) PublishOptions Either provide this or remote publish options
withPublishRemoteOptions (required) RemotePublishOptions Either provide this or publish options.
withMemberRole (optional) MemberRole MemberRole.PRESENTER Role of member to join channel as (used if not already in channel).
withStreamType (optional) StreamType StreamType.PRESENTATION Type of stream to publish.
withScreenName (optional) String <random unique string> Screen name of self member
withViewerStreamSelectionStrategy (optional) StreamSelectionStrategy StreamSelectionStrategy.MOST_RECENT This has to match the strategy channel viewers are using. When StreamSelectionStrategy.HIGH_AVAILABILITY is selected, then wildcard tokens will be generated for this as well as other streams published in this channel to ensure viewers can subscribe to any of them without having to first obtain a stream token.
buildPublishToChannelOptions <none> Builds the PublishToChannelOptions

Notes: * Wildcard token generation is always enabled when publishing to a channel

ChannelOptionsBuilder

Name Type Default Description
withName (required) String Name of channel
withAlias (optional) String <generated> Channel Alias. If not passed in, it will be generated for you.
withDescription (optional) String <empty> Channel description
buildChannelOptions <none> Builds the ChannelOptions

Publish To Channel Callback Arguments

Name Type Description
status RequestStatus The status of the operation
roomService RoomService Phenix room service
publisher ExpressPublisher Publisher object

Create a Channel

import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ChannelExpress;
import com.phenixrts.room.ChannelOptions;
import com.phenixrts.room.RoomServiceFactory;

final ChannelExpress channelExpress = ...;  // previously obtained

final ChannelOptions channelOptions = RoomServiceFactory.createChannelOptionsBuilder()
    .withName("MyAwesomeChannel")
    // Not required but if it is provided we will use this as the alias instead
    // of pre-generating one for you.
    .withAlias("MyAwesomeChannelAlias")
    .buildChannelOptions();

this.channelExpress.createChannel(
    channelOptions,
    (status, channel) -> {
      if (status != RequestStatus.OK) {
        // Handle room create error
        return;
      }

      // use `channel` to e.g. join
    });

Parameters

Name Type Description
options (required) ChannelOptions Options to create channel with
callback (required) Function Function to call on success/failure of creating to the channel. See Create Channel Callback Arguments

Express Create Channel Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
channel ImmutableRoom Immutable room object

Room Express

Single-step configuration based API for setting up a room - a solution for all your other streaming applications including Group Broadcast, Group Chat, and One to One Chat. The Room Express extends the lower-level Room Service API to provide easy solutions to:

Initializing

import android.content.Context;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.environment.android.AndroidContext;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PCastExpressOptions;
import com.phenixrts.express.RoomExpress;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.express.RoomExpressOptions;
import com.phenixrts.pcast.PCastInitializeOptions;

// 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 PCastExpressOptions pcastExpressOptions =
    PCastExpressFactory.createPCastExpressOptionsBuilder()
        .withBackendUri("https://example.yourdomain.com/phenix/")
        .withUnrecoverableErrorCallback((RequestStatus status, String description) -> {
          // Best to restart app, or attempt to re-create PCastExpress
        })
        .buildPCastExpressOptions();

final RoomExpressOptions roomExpressOptions =
    RoomExpressFactory.createRoomExpressOptionsBuilder()
        .withPCastExpressOptions(pcastExpressOptions)
        .buildPCastExpressOptions();

final RoomExpress roomExpress = RoomExpressFactory.createRoomExpress(roomExpressOptions);

RoomExpressOptionsBuilder

Name Type Default Description
withPCastExpressOptions (required) PCastExpressOptions See PCastExpressOptionsBuilder
buildRoomExpressOptions Builds the RoomExpressOptions

Join a Room

Join a room and optionally, automatically subscribe to member changes.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.JoinRoomOptions;
import com.phenixrts.express.RoomExpress;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.room.Member;
import com.phenixrts.room.RoomService;

final RoomExpress roomExpress = ...;  // previously obtained

final JoinRoomOptions joinRoomOptions = RoomExpressFactory.createJoinRoomOptionsBuilder()
    .withRoomAlias("myRoom42")
    .withCapabilities(new String[] {"streaming"})
    .buildJoinRoomOptions();

roomExpress.joinRoom(joinRoomOptions, (RequestStatus status, RoomService roomService) -> {
  if (status == RequestStatus.OK) {
    // Hold on to roomService reference for as long as you wish to stay in the room
  } else {
    // Handle error
  }
});

// With optional member update notification:
this.roomExpress.joinRoom(
    joinRoomOptions,
    (RequestStatus status, RoomService roomService) -> {
      if (status == RequestStatus.OK) {
        // Hold on to roomService reference for as long as you wish to stay in the room
      } else {
        // Handle error
      }
    },
    (Member[] members) -> {
      // Do something with room members
    });

Parameters

Name Type Description
options (required) JoinRoomOptions Options to join room with
joinRoomCallback (required) RoomExpress.JoinRoomCallback Function to call on success/failure of joining the room
membersChangedCallback (optional) RoomExpress.MembersChangedCallback Function to call on when the participant members in the room changes. Returns array of Members. Callback is guaranteed to be called at least once when room is joined.

JoinRoomOptionsBuilder

Name Type Default Description
withRoomId (required) String Id of channel to view
withRoomAlias (optional) String Alias, alternative to ID
withCapabilities (required) String[] The list of all capabilities to subscribe with.
withRole (optional) MemberRole MemberRole.AUDIENCE The Member Role to join with
withScreenName (optional) String The member screen name to join with. A random string will be generated if not provided.
withStreams (optional) Stream[] [] The member streams to join with. Empty if not provided
buildJoinRoomOptions Builds the JoinRoomOptions

Express Join Room Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
roomService RoomService Phenix room service object

Subscribe to a Member’s Stream

Subscribe to a room member’s stream and automatically handle audio and video state changes.

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.RoomExpress;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.express.SubscribeToMemberStreamOptions;
import com.phenixrts.room.ImmutableRoom;
import com.phenixrts.room.Member;
import com.phenixrts.room.Stream;
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;

final RoomExpress roomExpress = ...;  // previously obtained
final ImmutableRoom room = ...;  // previously obtained
final SurfaceView view = ...;  // previously obtained

final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

// Just an example showing how to get a stream from a member.
// In a real-world app you would want to subscribe to the room-members-observable on the room
// to receive updates when the list of members changes, and then subscribe to the streams-observable
// on each member to access their streams.
final Member member = room.getObservableMembers().getValue()[0];
final Stream memberStream = member.getObservableStreams().getValue()[0];

final SubscribeToMemberStreamOptions options =
    RoomExpressFactory.createSubscribeToMemberStreamOptionsBuilder()
        .withRenderer(renderSurface)
        .buildSubscribeToMemberStreamOptions();

roomExpress.subscribeToMemberStream(
    memberStream,
    options,
    (status, subscriber, renderer) -> {
      if (status != RequestStatus.OK) {
        // Handle subscribe error
        return;
      }

      // Important: Store subscriber reference, otherwise we will stop subscription immediately:
      //currentSubscriber = subscriber;
    });

Parameters

Name Type Description
memberStream (required) Stream The room member’s stream to subscribe to
options (required) Subscribe to Member’s Stream Options PCast Express Subscribe Options to subscribe to member stream with
callback (required) Function Function to call on success/failure of subscribing to the member stream

SubscribeToMemberStreamOptionsBuilder

Name Type Default Description
withCapabilities (optional) String[] [] The list of all capabilities to subscribe with
withRenderer (optional) AndroidVideoRenderSurface <none> Render surface on which to display stream. If none of the withRenderer... methods are called, no renderer will be instantiated.
withRenderer (optional) <none> false Will trigger instantiation of renderer. Useful for audio only type streams that do not require a render surface
withRendererOptions (optional) RendererOptions <none> Options passed to renderer. Will trigger instantiation of renderer
withMonitor (optional) MonitorOptions.SetupFailedCallback, MonitorOptions.StreamEndedCallback, MonitorOptions <none> Options for monitoring a subscriber for failure
withConnectOptions (optional) String[] [] List of options for subscribing
withTags (optional) String[] [] Tags for the stream
buildSubscribeToMemberStreamOptions <none> Builds the SubscribeToMemberStreamOptions

Subscribe To Member Stream Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
publisher Subscriber Phenix subscriber object
renderer Renderer Optional renderer if renderer was enabled, else nil

Publish to a Room

Publish local or remote media to a room. The room will be created if a room corresponding to the Room Options passed does not exist. If you have not entered the room via joinRoom or publishToRoom methods then a model for Self will be created.

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PublishOptions;
import com.phenixrts.express.PublishRemoteOptions;
import com.phenixrts.express.PublishToRoomOptions;
import com.phenixrts.express.RoomExpress;
import com.phenixrts.express.RoomExpressFactory;
import com.phenixrts.pcast.DeviceCapability;
import com.phenixrts.pcast.FacingMode;
import com.phenixrts.pcast.RendererOptions;
import com.phenixrts.pcast.UserMediaOptions
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;
import com.phenixrts.room.MemberRole;
import com.phenixrts.room.RoomOptions;
import com.phenixrts.room.RoomServiceFactory;
import com.phenixrts.room.StreamType;

final RoomExpress roomExpress = ...;  // previously obtained
final SurfaceView view = ...;  // previously obtained

final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

final UserMediaOptions mediaConstraints = new UserMediaOptions();
// Customize constraints if needed

final PublishOptions publishOptions = PCastExpressFactory.createPublishOptionsBuilder()
    .withCapabilities(new String[]{"hd", "streaming"})
    .withMediaConstraints(mediaConstraints)
    .withPreviewRenderer(renderSurface)
    .buildPublishOptions();

// Using RoomOptions means that the room may or may not already exist.
// If the room ID is known in advance, it is recommended to use `withRoomId` instead
// of `withRoomOptions` when assembling the `PublishToRoomOptions` below
final RoomOptions roomOptions = RoomServiceFactory.createRoomOptionsBuilder()
    .withName("MyAwesomeRoom")
    .buildRoomOptions();

final PublishToRoomOptions localPublishToRoomOptions =
    RoomExpressFactory.createPublishToRoomOptionsBuilder()
        .withStreamType(StreamType.USER)
        .withMemberRole(MemberRole.PARTICIPANT)
        .withRoomOptions(roomOptions)
        .withPublishOptions(publishOptions)
        .buildPublishToRoomOptions();

roomExpress.publishToRoom(
    localPublishToRoomOptions,
    (publishStatus, roomService, publisher, previewRenderer) -> {
      if (publishStatus != RequestStatus.OK) {
        // Handle channel publish error
        return;
      }

      // Important: Store publisher reference, otherwise we will stop publishing again immediately:
      currentPublisher = publisher;
    }
);

// OR for a remote stream:
final PublishRemoteOptions remotePublishOptions = PCastExpressFactory
    .createPublishRemoteOptionsBuilder()
    .withStreamUri("http://example.com/mystream.mp4")
    .buildPublishRemoteOptions();

final PublishToRoomOptions remotePublishToRoomOptions =
    RoomExpressFactory.createPublishToRoomOptionsBuilder()
        .withStreamType(StreamType.USER)
        .withMemberRole(MemberRole.PARTICIPANT)
        .withRoomOptions(roomOptions)
        .withPublishRemoteOptions(remotePublishOptions)
        .buildPublishToRoomOptions();

// Remaining code is the same as for local stream

Parameters

Name Type Description
options (required) Options Options to publish to room with
callback (required) Function Function to call on success/failure of publishing to the room

PublishToRoomOptionsBuilder

Name Type Default Description
withRoomOptions (required) RoomOptions) If omitted, then withRoomId needs to be provided.
withRoomId (required) String ID of room to publish to. If omitted, then withRoomOptions needs to be provided.
withPublishOptions (required) PublishOptions Either local or remote publish options are required
withPublishRemoteOptions (required) PublishRemoteOptions Either local or remote publish options are required
withMemberRole (required) MemberRole Role of member to join room as (used if not already in room). See Member Roles
withStreamType (required) StreamType Type of stream to publish. See Stream Types
withScreenName (optional) String <automatically generated> Screen name of self member
withViewerStreamSelectionStrategy (optional) StreamSelectionStrategy StreamSelectionStrategy.MOST_RECENT Stream Selection Strategy
withWildcardTokens (optional) true Generate wildcard stream tokens to be appended to member’s stream uri. Reduces time for subscribing to published stream.
buildPublishToRoomOptions <none> Builds the PublishToRoomOptions

Publish To Room Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
roomService RoomService Phenix room service
publisher Publisher Phenix publisher object
previewRenderer Renderer Optional renderer if preview renderer was enabled

Get PCast Express

Get the underlying instance of the PCast Express. This is preferred to creating another instance as this will introduce more overhead.

import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.RoomExpress;

final RoomExpress roomExpress = ...;  // previously obtained

final PCastExpress pcastExpress = roomExpress.getPCastExpress();

Clean Up

Underlying resources are kept alive for as long as you hold any references to any of the returned objects (room service, subscriber, renderer) an do not call close on them. Once those references as well as any reference to the room express instance itself have been released (or close has been called), all underlying resources will be automatically cleaned up and released.

PCast Express

The PCast Express extends the PCast api to provide a single-step configuration based API for:

This API is intended to be used as a supplement to the Room Express although it can be used to stream all on its own.

Initializing

import android.content.Context;

import com.phenixrts.common.RequestStatus;
import com.phenixrts.environment.android.AndroidContext;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PCastExpressOptions;
import com.phenixrts.pcast.PCastInitializeOptions;

// 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 PCastExpressOptions pcastExpressOptions =
    PCastExpressFactory.createPCastExpressOptionsBuilder()
        .withBackendUri("https://example.yourdomain.com/phenix/")
        .withUnrecoverableErrorCallback((RequestStatus status, String description) -> {
          // Best to restart app, or attempt to re-create PCastExpress
        })
        .buildPCastExpressOptions();

final PCastExpress pcastExpress = PCastExpressFactory.createPCastExpress(pcastExpressOptions);

PCastExpressOptionsBuilder

Name Type Default Description
withBackendUri (required) String Url to your backend. We send requests here to authenticate and get streaming tokens.
withAuthenticationData (optional) String Your authentication data for the user. On every request, this will be sent to your backend through a HTTP POST request and all its attributes would be accessible on the request body. Needs to be valid JSON.
withAuthenticationToken (optional) String The authentication token generated using the Admin API. If not passed one will be generated automatically by querying the provided backend uri (may happen multiple times depending on the lifetime of the session).
withUnrecoverableErrorCallback (optional) PCastExpressOptions.UnrecoverableErrorCallback Function to be called when authentication fails or a failure occurs that is unrecoverable.
withPCastUri (optional) String Allows overriding default PCast URI.
withPCastInitializationOptions (optional) PCastInitializeOptions Use custom options when initializing PCast.
withAuthenticationRouteOverride (optional) String auth Allows override of default route for authentication tokens
withStreamRouteOverride (optional) String stream Allows override of default route for stream tokens
buildPCastExpressOptions Builds the PCastExpressOptions

Publishing Local Media

Publish local user media:

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ExpressPublisher;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PublishOptions;
import com.phenixrts.pcast.AudioEchoCancelationMode;
import com.phenixrts.pcast.DeviceCapability;
import com.phenixrts.pcast.DeviceConstraint;
import com.phenixrts.pcast.FacingMode;
import com.phenixrts.pcast.Renderer;
import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;
import java.util.Arrays;

final PCastExpress pcastExpress = ...;  // previously obtained

final UserMediaOptions userMediaConstraints = new UserMediaOptions();

userMediaConstraints.getVideoOptions().enabled = true;
userMediaConstraints.getVideoOptions().capabilityConstraints.put(
    DeviceCapability.FACING_MODE,
    Arrays.asList(new DeviceConstraint(FacingMode.USER)));

userMediaConstraints.getAudioOptions().enabled = true;
userMediaConstraints.getAudioOptions().capabilityConstraints.put(
    DeviceCapability.AUDIO_ECHO_CANCELATION_MODE,
    Arrays.asList(new DeviceConstraint(AudioEchoCancelationMode.ON)));

final PublishOptions publishOptions = PCastExpressFactory.createPublishOptionsBuilder()
    .withCapabilities(new String[] {"real-time"})
    .withMediaConstraints(userMediaConstraints)
    .buildPublishOptions();

pcastExpress.publish(publishOptions, (RequestStatus status, ExpressPublisher publisher) -> {
  if (status == RequestStatus.OK) {
    // Do something with publisher
  } else {
    // Handle error
  }
});


// Create a publisher with an automatically started preview renderer
final SurfaceView view = ...;  // previously obtained
final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

final PublishOptions publishOptionsWithPreview = PCastExpressFactory.createPublishOptionsBuilder()
    .withCapabilities(new String[] {"real-time"})
    .withMediaConstraints(userMediaConstraints)
    .withPreviewRenderer(renderSurface)
    .buildPublishOptions();

pcastExpress.publish(
    publishOptions, (RequestStatus status, ExpressPublisher publisher, Renderer preview) -> {
  if (status == RequestStatus.OK) {
    // Do something with publisher and preview renderer
  } else {
    // Handle error
  }
});

Notes:

Parameters

Name Type Description
options (required) PublishOptions Publish options
callback (required) PCastExpress.PublishCallback or PCastExpress.PublishWithPreviewCallback Callback for error/success handling

PublishOptionsBuilder

Name Type Default Description
withMediaConstraints (required) UserMediaOptions getUserMedia options Constraints to get the user media.
withUserMedia (optional) UserMediaStream alternative to withMediaConstraints - you can pass user media stream returned from getUserMedia.
withCapabilities (optional) String[] The list of all capabilities to publish with. Default is empty array.
withPreviewRenderer (optional) AndroidVideoRenderSurface Render layer on which to display local preview. If none of the withPreview... methods are called, no preview renderer will be instantiated.
withPreviewRenderer (optional) Will trigger instantiation of preview renderer. Useful for audio only type streams that do not require a render surface.
withPreviewRendererOptions (optional) RendererOptions Options passed to preview renderer. Will trigger instantiation of preview renderer.
withMonitor (optional) MonitorOptions.SetupFailedCallback, MonitorOptions.StreamEndedCallback, MonitorOptions Options for monitoring a publisher for failure.
withConnectOptions (optional) Strings[] List of options for publishing.
withTags (optional) Strings[] Tags for the stream.
withStreamToken (optional) String The publish token generated using the Admin API. If not passed one will be generated automatically by querying the backend uri that was passed when instantiating the Express API.
buildPublishOptions Builds the PublishOptions

Callback Arguments

Name Type Description
status RequestStatus The status of the operation
publisher ExpressPublisher Publisher object
previewRenderer Renderer Optionally provided if any of the withPreview... methods were called on the options builder and publish is invoked with withPreview.

ExpressPublisher

Shares most methods with regular Publisher returned by PCast, see Publish a Stream.

Name Signature Returns Description
stop () void Stops publisher. Subscribers will receive stream ended.
stop (reason) void Stops publisher with a custom reason. Subscribers will receive StreamEndedReason.CUSTOM reason.
enableAudio () void Unmutes audio.
disableAudio () void Mutes audio.
enableVideo () void Unmutes video.
disableVideo () void Mutes video (black frames).
setDataQualityChangedCallback (callback) void Listen for Data Quality Feedback
limitBandwidth (bandwidthLimitInBps) Disposable Temporarily limit published video bitrate, see Limit Bitrate
getStreamId () String Returns stream ID of publisher
hasEnded () bool Indicates whether publisher has ended, e.g. by stop having been invoked

Publishing Remote Media (Ingest)

Publish from remote sources into the Phenix platform. This enables us to distribute your source media using the Phenix platform. After publishing users may subscribe to the stream using either pcast subscribe or express subscribe.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ExpressPublisher;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.PublishRemoteOptions;

final PCastExpress pcastExpress = ...;  // previously obtained

final PublishRemoteOptions publishRemoteOptions = PCastExpressFactory.createPublishRemoteOptionsBuilder()
    .withStreamUri("http://mycdn.example.com/mystream.mp4")
    .withCapabilities(new String[] {})
    .buildPublishRemoteOptions();

pcastExpress.publishRemote(publishRemoteOptions, (RequestStatus status, ExpressPublisher publisher) -> {
  if (status == RequestStatus.OK) {
    // Do something with publisher
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
options (required) PublishRemoteOptions Publish Remote options
callback (required) PCastExpress.PublishCallback Callback for error/success handling

PublishRemoteOptionsBuilder

Name Type Default Description
withCapabilities (required) String[] The list of all capabilities to publish with.
withStreamUri (required) String Link to remote media (mp4, rtmp, etc.)
withStreamToken (optional) String The publish token generated using the Admin API. If not passed one will be generated automatically by querying the backend uri that was passed when instantiating the Express API.
withConnectOptions (optional) Strings[] List of options for publishing from a remote source.
withTags (optional) Strings[] Tags for the stream
withMaximumFrameRateConstraint (optional) double Maximum frame rate constraint.
withExactFrameRateConstraint (optional) double Exact frame rate constraint.
withPrerollSkipDuration (optional) long 500 The amount of time to skip at the beginning of the media in milliseconds.
buildPublishRemoteOptions Builds the PublishRemoteOptions

Callback Arguments

Name Type Description
status RequestStatus The status of the operation. See Publish Callback Status Codes
publisher ExpressPublisher Phenix publisher object

Subscribing to Published Media

Subscribe to streams published with the Phenix platform

import android.view.SurfaceView;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ExpressSubscriber;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.SubscribeOptions;
import com.phenixrts.pcast.Renderer;
import com.phenixrts.pcast.android.AndroidVideoRenderSurface;

final PCastExpress pcastExpress = ...;  // previously obtained
final SurfaceView view = ...;  // previously obtained

final AndroidVideoRenderSurface renderSurface = new AndroidVideoRenderSurface(view.getHolder());

final SubscribeOptions subscribeOptions = PCastExpressFactory.createSubscribeOptionsBuilder()
    .withStreamId("us-west#us-west1-b.zzzzzzzz.20000000.xxxxxxxx")
    .withCapabilities(new String[] {"streaming"})
    .withRenderer(renderSurface)
    .buildSubscribeOptions();

pcastExpress.subscribe(
    subscribeOptions,
    (RequestStatus status, ExpressSubscriber subscriber, Renderer renderer) -> {
  if (status == RequestStatus.OK) {
    // Do something with subscriber

    if (renderer != null) {
      // Returned if `withRenderer...` option was enabled - Do something with renderer
    }
  } else {
    // Handle error
  }
});

Notes:

Parameters

Name Type Description
options (required) SubscribeOptions Subscribe options
callback (required) PCastExpress.SubscribeCallback Callback for error/success handling

Subscribe Options

Name Type Default Description
withStreamId (required) String The stream ID of the published stream
withCapabilities (required) String[] The list of all capabilities to subscribe with.
withStreamToken (optional) String The subscribe token generated using the Admin API. If not passed one will be generated automatically by querying the backend uri that was passed when instantiating the Express API.
withRenderer (optional) AndroidVideoRenderSurface Render layer on which to display stream. If none of the withRenderer... methods are called, no renderer will be instantiated.
withRenderer (optional) Will trigger instantiation of renderer. Useful for audio only type streams that do not require a render surface.
withRendererOptions (optional) RendererOptions Options passed to renderer. Will trigger instantiation of renderer.
withMonitor (optional) MonitorOptions.SetupFailedCallback, MonitorOptions.StreamEndedCallback, MonitorOptions Options for monitoring a subscriber for failure.
withConnectOptions (optional) String[] List of options for subscribing.
withTags (optional) NSString[] Tags for the stream
buildSubscribeOptions Builds the SubscribeOptions

ExpressSubscriber

Shares most methods with regular MediaStream returned by PCast, see Subscribe to a Stream.

Name Signature Returns Description
createRenderer () Renderer Creates a new renderer. This should only be called if none of the withRenderer... builder methods were invoked.
createRenderer (RendererOptions) Renderer Creates a new renderer with RendererOptions. This should only be called if none of the withRenderer... builder methods were invoked.
getAudioTracks () MediaStreamTrack[] Returns all associated audio tracks of this stream
getVideoTracks () MediaStreamTrack[] Returns all associated video tracks of this stream
getTracks () MediaStreamTrack[] Returns all associated tracks of this stream
stop () void Stops subscription. This will trigger the stream ended event.
disableAudio () void Mutes audio.
enableVideo () void Unmutes video.
disableVideo () void Mutes video (black frames).

Monitor

Note: On Android, the monitor options are currently ignored, but the callbacks for stream setup and stream ended will be triggered.

Monitor callbacks and options can be passed to subscribe and publish options builders. The first callback gets invoked only when we internally fail to setup a stream. The second callback gets invoked whenever a stream ends, whether it is due to failure or not. The retry OptionalAction allows you to retry publishing or subscribing the failed stream. You must test first whether there is a retry action present by calling isPresent as it may not be possible to retry the stream (example: a stream that ended normally cannot be retried). You should call dismiss on the retry action to dismiss it, close has the same effect. You also may defer invoking the retry action.

Example Monitor for subscribing

import com.phenixrts.common.OptionalAction;
import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.ExpressSubscriber;
import com.phenixrts.express.MonitorOptions;
import com.phenixrts.express.PCastExpressFactory;
import com.phenixrts.express.SubscribeOptions;
import com.phenixrts.pcast.StreamEndedReason;

final MonitorOptions monitorOptions = PCastExpressFactory.createMonitorOptionsBuilder()
    .buildMonitorOptions();

final SubscribeOptions subscribeOptions = PCastExpressFactory.createSubscribeOptionsBuilder()
    .withStreamId("us-west#us-west1-b.zzzzzzzz.20000000.xxxxxxxx")
    .withCapabilities(new String[] {"real-time"})
    .withMonitor(
        (RequestStatus status, OptionalAction retry) -> {
          if (retry.isPresent()) {
            if (shouldRetry()) {  // <- Your logic goes here
              retry.perform();
            } else {
              retry.dismiss();
            }
          }
        },
        (StreamEndedReason reason, String description, OptionalAction retry) -> {
          if (retry.isPresent()) {
            if (reason == StreamEndedReason.FAILED) {  // <- Just an example
              retry.perform();
            } else {
              retry.dismiss();
            }
          }
        },
        monitorOptions)
    .buildSubscribeOptions();

OptionalAction

Name Signature Returns Description
perform () void Performes the action. This will cause a failure if isPresent is false.
dismiss () void Dismisses the action (if any). Can be called multiple times, will result in isPresent to return false. Invoking close has same effect.
isPresent () boolean Indicates whether an action can be performed.

MonitorSetupFailedCallback Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
retry OptionalAction Optionally allow retrying the failed stream.

MonitorStreamEndedCallback Callback Arguments

Name Type Description
reason StreamEndedReason Reason for stream ended.
description String Optional additional ended reason description. Carries custom message.
retry OptionalAction Optionally allow retrying the failed stream. For normally ended streams isPresent will always return false.

Express Get User Media

Get local user media. For now this is merely a wrapper around Get Local User Media.

import com.phenixrts.common.RequestStatus;
import com.phenixrts.express.PCastExpress;
import com.phenixrts.pcast.UserMediaOptions;
import com.phenixrts.pcast.UserMediaStream;

final PCastExpress pcastExpress = ...;  // previously obtained

final UserMediaOptions userMediaOptions = new UserMediaOptions();

pcastExpress.getUserMedia(userMediaOptions, (RequestStatus status, UserMediaStream userMedia) -> {
  if (status == RequestStatus.OK) {
    // Do something with user media stream
  } else {
    // Handle error
  }
});

Parameters

Name Type Description
options (required) UserMediaOptions User media options
callback (required) PCastExpress.GetUserMediaCallback Callback for error/success handling

Callback Arguments

Name Type Description
status RequestStatus The status of the operation.
userMedia UserMediaStream User media stream

Get PCast

Get the underlying instance of the PCast. This is preferred to creating another instance as this will introduce more overhead.

import com.phenixrts.express.PCastExpress;
import com.phenixrts.pcast.PCast;

final PCastExpress pcastExpress = ...;  // previously obtained

final PCast pcast = pcastExpress.getPCast();

Clean up

Subscribers and publishers are kept alive for as long as they are being referenced in your app. To force SDK objects to release their resources, you can call close on them . PCastExpress will only shutdown once it is no longer being referenced or close has been called on it.

Override Playout Delay

Example Code for overriding the playout delay via a MediaStream object

import com.phenixrts.common.Disposable;
import com.phenixrts.pcast.MediaStream;
import com.phenixrts.pcast.Renderer;

class SomeClass {
  private Disposable currentRendererPlayoutDelayOverride;
  private Renderer currentRenderer;

  private void setPlayoutDelayOverrideFor10Seconds() {
    final MediaStream mediaStream = ...;  // Previously obtained
    this.currentRenderer = mediaStream.createRenderer();

    // Override playout delay to 900ms for 10 seconds
    final long playoutDelayInMilliseconds = 900

    this.currentRendererPlayoutDelayOverride = this.currentRenderer.overridePlayoutDelay(playoutDelayInMilliseconds);

    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
      @Override
      public void run() {
        SomeClass.this.currentRendererPlayoutDelayOverride.dispose();
        SomeClass.this.currentRendererPlayoutDelayOverride = null;
      }
    }, 10000);
  }
}

Example Code for limiting video bandwidth with a ExpressSubscriber object

import com.phenixrts.common.Disposable;
import com.phenixrts.express.ExpressSubscriber;
import com.phenixrts.pcast.Renderer;

class SomeClass {
  private Disposable currentRendererPlayoutDelayOverride;
  private Renderer currentRenderer;

  private void setPlayoutDelayOverrideFor10Seconds() {
    final ExpressSubscriber subscriber = ...;  // Previously obtained
    this.currentRenderer = subscriber.createRenderer();

    // Override playout delay to 900ms for 10 seconds
    final long playoutDelayInMilliseconds = 900

    this.currentRendererPlayoutDelayOverride = this.currentRenderer.overridePlayoutDelay(playoutDelayInMilliseconds);

    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
      @Override
      public void run() {
        SomeClass.this.currentRendererPlayoutDelayOverride.dispose();
        SomeClass.this.currentRendererPlayoutDelayOverride = null;
      }
    }, 10000);
  }
}

The playout delay represents the amount of time by which audio and video are delayed when content is rendered by a subscriber; i.e. it works as a buffer. The delay adds to the overall end-to-end latency experienced by the user (on top of capture, encoding, and network latencies). It is necessary to handle network-related fluctuations (such as jitter or data loss). By default, the playout delay for real-time streams is set to 230ms. The following API allows app developers to override this default value.

In order to access the API, a reference to a Renderer is needed. It can be obtained either by creating it from MediaStream or ExpressSubscriber, or via several of the Express APIs, which can return renderers (joinChannel, subscribeToMemberStream, subscribe).

The returned disposable allows control over how long the override should stay in effect; it therefore needs to be held onto via a strong reference. If overridePlayoutDelay 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. Releasing any of the disposables from earlier overridePlayoutDelay calls will have no effect.

Notes:

Parameters

Name Type Description
desiredPlayoutDelayInMilliseconds (required) long Desired playout delay

Returns

Type Description
Disposable Ensures override is kept in effect for as long as a strong reference is held

Android Examples

You can find our Android examples on GitHub.

WebView

A simple example that integrates WebView to load a web page using the Phenix Web SDK.