PCast™ Express - Beta
The PCast™ Express extends the PCast™ API to provide a single-step configuration based API for:
- Publishing local media
- Subscribing to published streams
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((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 |
---|---|---|---|
withAuthenticationToken (required) | String | The authentication token generated using the Phenix EdgeAuth library. | |
withPCastUri (optional) | String | Allows overriding default PCast™ URI. | |
withPCastInitializationOptions (optional) | PCastInitializeOptions | Use custom options when initializing PCast™. | |
buildPCastExpressOptions | none | Builds the PCastExpressOptions |
Publishing Local Media
Publish local user media:
- Camera
- Microphone
- Screen (on supported devices)
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.AudioEchoCancellationMode;
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_CANCELLATION_MODE,
Arrays.asList(new DeviceConstraint(AudioEchoCancellationMode.ON)));
final PublishOptions publishOptions = PCastExpressFactory.createPublishOptionsBuilder()
.withStreamToken("DIGEST:eyJhc...")
.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()
.withStreamToken("DIGEST:eyJhc...")
.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:
- The preview renderer will already have been started by the time it is received by your callback
- The publisher will remain active for as long as you keep a reference to it
- Force release of the publisher by invoking
dispose
on it
Publisher 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. | |
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) | none | 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. | |
withTags (optional) | Strings[] | Tags for the stream. | |
withStreamToken (required) | String | The publish token generated using the Phenix EdgeAuth library. | |
buildPublishOptions | none | Builds the PublishOptions |
ExpressPublisher 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 |
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")
.withStreamToken("DIGEST:eyJhc...")
.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:
- The renderer will already have been started by the time it is received by your callback
- If a renderer is provided, your ExpressSubscriber will be kept alive for as long as you are referencing that renderer. There is no need to also store a reference to the subscriber in that case
- Once subscriber and renderer references have been released, the renderer and subscription will automatically be stopped
- To force release renderer or subscriber, you can invoke
dispose
, which will dispose the underlying resources
Subscribe 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 | |
withStreamToken (required) | String | The subscribe token generated using the Phenix EdgeAuth library. | |
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) | none | 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. | |
withTags (optional) | NSString[] | Tags for the stream | |
buildSubscribeOptions | none | 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). |
subscribeToNielsenId3Tag | (NielsenId3TagCallback, NielsenId3ErrorCallback) | Disposable | Register a callback to be called when a new Nielsen ID3 tag is received. When the error callback is called, no further notifications are emitted. You may re-subscribe in the error callback if you want to keep receiving Nielsen ID3 tag after an error. |
NielsenId3TagCallback Arguments
Name | Type | Description |
---|---|---|
data | byte[] | The latest Nielsen ID3 tag data |
NielsenId3ErrorCallback Arguments
Name | Type | Description |
---|---|---|
message | String | The message describing what caused the error |
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, dispose
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")
.withStreamToken("DIGEST:eyJhc...")
.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 | Performs 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 dispose 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
}
});
Express Get User Media Parameters
Name | Type | Description |
---|---|---|
options (required) | UserMediaOptions | User media options |
callback (required) | PCastExpress.GetUserMediaCallback | Callback for error/success handling |
Express Get User Media 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();
Handling authentication failure
To observe SDK AuthenticationStatus:
import com.phenixrts.common.AuthenticationStatus;
import com.phenixrts.common.Disposable;
Disposable disposable = ... // declare a property
this.disposable = pcastExpress.getObservableAuthenticationStatus().subscribe((change) -> {
if (change == AuthenticationStatus.UNAUTHENTICATED) {
this.handleUnauthenticatedStatus();
}
});
To fetch a new fresh Authentication token and set a new fetched token to SDK:
import com.phenixrts.common.RequestStatus;
void fetchANewAuthenticationToken(closure: (token: String) -> Unit) {
// This method calls a client backend to fetch a new authentication token generated using [EdgeAuth library](../api/#creating-authentication-and-stream-tokens).
// and returns a fetched token using closure callback
}
void handleUnauthenticatedStatus() {
fetchANewAuthenticationToken((token) -> {
// Set a new token to SDK
final RequestStatus status = this.pcastExpress.setAuthenticationToken(token)
if (status != PhenixRequestStatus.ok) {
// Handle error when setting a new token
// This situation can only occur if the token is invalid and cannot be processed by the SDK.
}
});
}
PCastExpress
Name | Signature | Returns | Description |
---|---|---|---|
setAuthenticationToken | (String) | RequestStatus | Setting an authentication token in the SDK triggers re-authentication if the current SDK AuthenticationStatus is unauthenticated. Otherwise, the new authentication token will be used during the next SDK re-authentication attempt. |
getObservableAuthenticationStatus | () | Observable(of AuthenticationStatus) | Get the observable of the SDK authentication status. Use this to refresh the SDK's authentication token when the AuthenticationStatus becomes 'unauthenticated'. |
When the SDK attempts to authenticate with an expired token, it receives an "unauthenticated" status. In this state, publish and subscribe operations are not possible.
The code sample shows how to re-authenticate the SDK when necessary.
Notes:
-
The implementation of
ObservableAuthenticationStatus
subscriber should be asynchronous and should not contain any blocking operations. -
The client
fetchANewAuthenticationToken
method should be executed as quickly as possible. During the “unauthenticated” state, the SDK will be unable to perform publish or subscribe operations. -
Attempts to publish and subscribe, when the SDK's authentication status is not authenticated, will wait for successful authentication for 18 seconds before timing out.
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 dispose
on them . PCastExpress will only shut down once it is no longer being referenced or dispose
has been called on it.