iOS SDK
The iOS SDK APIs are split into two main categories: Express and Low-Level. We recommend that you start with the Express API as this:
- Simplifies integration
- Automatically recovers from stream failures
- 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 private Cocoapod. Please check our private Github repository for details.
Debugging
In order to identify issues it can be helpful to see what is happening at the Phenix SDK level. Phenix logs are disabled by default in the Xcode console. To enable them, set the following environment variable (via Xcode menu: Product -> Scheme -> Edit Scheme… -> Run -> Arguments -> Environment Variables):
Name: PHENIX_LOGGING_CONSOLE_LOG_LEVEL
Value: Debug
To reduce output, you can change the value to Info
or Warn
for instance. You will only be able to view these logs when either running in the simulator or with a device attached to Xcode.
You can also retrieve the Phenix SDK logs programmatically using the collectLogMessages
PCast API.
@import PhenixSdk;
id<PhenixPCast> pcast = ...; // previously obtained
[pcast collectLogMessages:^(id<PhenixPCast> pcast, PhenixRequestStatus status, NSString* messages) {
if (status != PhenixRequestStatusOk) {
// Handle error
return;
}
NSArray* messagesArray = [messages componentsSeparatedByString:@"\n"];
for (NSString* message in messagesArray) {
// Do something with 'message'
}
}];
import PhenixSdk
let pcast: PhenixPCast = ... // previously obtained
pcast.collectLogMessages({ (pcast: PhenixPCast?, status: PhenixRequestStatus, messages: String?) in
guard let messages = messages, status == .ok else {
// Handle error
return
}
let messagesArray = messages.components(separatedBy: "\n")
for message in messagesArray {
// Do something with 'message'
}
})
Notes:
messages
string contains the most recent as well as initial logs (since your app started)- Each log entry in the string is separated by a newline
- Log entries are stored chronologically, newest logs last
Express iOS SDK
Our Express APIs provide a simple, single-step API for the easiest integration of streaming into your application
Common Use Cases
- Channel (one to many) -> Channel
- Group Broadcast (few to many) -> Room
- Group Chat (many to many) -> Room
- One to One Chat -> Room
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:
- View a channel
- Publish to a channel
- Create a channel
Initializing
import PhenixSdk
let backendEndpointUri = "https://example.yourdomain.com/phenix/"
let pcastExpressOptions = PhenixPCastExpressFactory.createPCastExpressOptionsBuilder()
.withBackendUri(backendEndpointUri)
.buildPCastExpressOptions()
let roomExpressOptions = PhenixRoomExpressFactory.createRoomExpressOptionsBuilder()
.withPCastExpressOptions(pcastExpressOptions)
.buildRoomExpressOptions()
let channelExpressOptions = PhenixChannelExpressFactory.createChannelExpressOptionsBuilder()
.withRoomExpressOptions(roomExpressOptions)
.buildChannelExpressOptions()
let channelExpress = PhenixChannelExpressFactory.createChannelExpress(channelExpressOptions)
ChannelExpressOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withRoomExpressOptions (required) | PhenixRoomExpressOptions | See PhenixRoomExpressOptionsBuilder | |
buildChannelExpressOptions | <none> | Builds the PhenixChannelExpressOptions |
View a Channel
Join a channel and automatically view the most recent content published to that channel.
import PhenixSdk
let channelExpress: PhenixChannelExpress = ... // previously obtained
let renderLayer: CALayer = ... // previously obtained
// Just an example (you can omit renderer options if defaults are ok)
let rendererOptions = PhenixRendererOptions()
options.aspectRatioMode = .letterbox
let joinRoomOptions = PhenixRoomExpressFactory.createJoinRoomOptionsBuilder()
.withRoomAlias("MyAwesomeChannel")
.buildJoinRoomOptions()
let joinChannelOptions = PhenixChannelExpressFactory.createJoinChannelOptionsBuilder()
.withJoinRoomOptions(joinRoomOptions)
.withStreamSelectionStrategy(.mostRecent)
.withRenderer(renderLayer)
.withRendererOptions(rendererOptions)
.buildJoinChannelOptions()
channelExpress.joinChannel(
joinChannelOptions,
{ [weak self] (requestStatus: PhenixRequestStatus, roomService: PhenixRoomService?) in
guard requestStatus == .ok, let strongSelf = self else {
// Handle channel join error
return
}
// Important: Store room service reference, otherwise we will leave channel again immediately:
strongSelf.currentRoomService = roomService
},
{ [weak self]
(requestStatus: PhenixRequestStatus,
expressSubscriber: PhenixExpressSubscriber?,
renderer: PhenixRenderer?) in
guard let strongSelf = self else {
return
}
if (requestStatus == .ok) {
// Successfully subscribed to a stream. No need to hold on to any references
} else if (requestStatus == .noStreamPlaying) {
// No stream playing in channel, update UI accordingy
} else {
// We failed to subscribe and retry attempts must have failed
}
});
Parameters
Name | Type | Description |
---|---|---|
options (required) | Options | Options to join channel with |
joinChannelCallback (required) | Function | Function to call on success/failure of joining the channel. |
subscriberCallback (required) | Function | Function to call on when the most recent presenter changes. |
PhenixJoinChannelOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withJoinRoomOptions (required) | PhenixJoinRoomOptions | See PhenixJoinRoomOptionsBuilder. | |
withRenderer (optional) | CALayer | 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) | PhenixRendererOptions | Options passed to renderer. Will trigger instantiation of renderer. | |
withStreamSelectionStrategy (optional) | PhenixStreamSelectionStrategy | PhenixStreamSelectionStrategyMostRecent | Determines how member streams are selected for subscriptions. |
buildJoinChannelOptions | <none> | Builds the PhenixJoinChannelOptions |
Stream Selection Strategy
Strategy | Description |
---|---|
PhenixStreamSelectionStrategyMostRecent | Select the most recent stream. Viewing stream changes any time a stream starts or is updated in the room. |
PhenixStreamSelectionStrategyHighAvailability | 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 | PhenixRoomService | Phenix room service object |
View Channel Subscriber Callback Status Codes
Status | Description |
---|---|
PhenixRequestStatusOk | Successfully subscribed to presenter |
PhenixRequestStatusNoStreamPlaying | No presenter in channel to subscribe to. Wait for presenter to join. |
<varies> |
Publish to a Channel
Publish a local or remote media to a channel. Users that are viewing the channel will see your media.
import PhenixSdk
let channelExpress: PhenixChannelExpress = ... // previously obtained
let renderLayer: CALayer = ... // previously obtained
// 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 `PhenixPublishToChannelOptions` below
let channelOptions = PhenixRoomServiceFactory.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
let mediaConstraints = PhenixUserMediaOptions()
mediaConstraints.video.capabilityConstraints[PhenixDeviceCapability.facingMode.rawValue] =
[PhenixDeviceConstraint.initWith(.user)]
mediaConstraints.video.capabilityConstraints[PhenixDeviceCapability.frameRate.rawValue] =
[PhenixDeviceConstraint.initWith(15)]
mediaConstraints.video.capabilityConstraints[PhenixDeviceCapability.height.rawValue] =
[PhenixDeviceConstraint.initWith(720)]
mediaConstraints.video.capabilityConstraints[PhenixDeviceCapability.width.rawValue] =
[PhenixDeviceConstraint.initWith(1280)]
mediaConstraints.audio.capabilityConstraints[PhenixDeviceCapability.audioEchoCancelationMode.rawValue] =
[PhenixDeviceConstraint.initWith(PhenixAudioEchoCancelationMode.on)]
let publishOptions = PhenixPCastExpressFactory.createPublishOptionsBuilder()
.withCapabilities(["hd", "streaming"]) // Example capabilities
.withMediaConstraints(mediaConstraints)
.withPreviewRenderer(renderLayer)
.buildPublishOptions()
let publishToChannelOptions = PhenixChannelExpressFactory.createPublishToChannelOptionsBuilder()
.withChannelOptions(channelOptions)
.withPublishOptions(publishOptions)
.buildPublishToChannelOptions()
channelExpress.publish(
toChannel: publishToChannelOptions,
withPreviewCallback: { [weak self] (
status: PhenixRequestStatus,
roomService: PhenixRoomService?,
publisher: PhenixExpressPublisher?,
previewRenderer: PhenixRenderer?) in
guard status == .ok, let strongSelf = self else {
// Handle channel publish error
return
}
// Important: Store publisher reference, otherwise we will stop publishing again immediately:
strongSelf.currentPublisher = publisher
})
// OR (without a preview):
channelExpress.publish(
toChannel: publishToChannelOptions,
withCallback: { [weak self] (
status: PhenixRequestStatus,
roomService: PhenixRoomService?,
publisher: PhenixExpressPublisher?) in
guard status == .ok, let strongSelf = self else {
// Handle channel publish error
return
}
// Important: Store publisher reference, otherwise we will stop publishing again immediately:
strongSelf.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 |
PhenixPublishToChannelOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withChannelOptions (required) | PhenixChannelOptions | See PhenixChannelOptionsBuilder. 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) | PhenixPublishOptions | Either provide this or remote publish options. | |
withPublishRemoteOptions (required) | PhenixRemotePublishOptions | Either provide this or publish options. | |
withMemberRole (optional) | PhenixMemberRole | PhenixMemberRolePresenter | Role of member to join channel as (used if not already in channel). |
withStreamType (optional) | PhenixStreamType | PhenixStreamTypePresentation | Type of stream to publish |
withScreenName (optional) | String | <random unique string> | Your screen name |
withViewerStreamSelectionStrategy (optional) | PhenixStreamSelectionStrategy | PhenixStreamSelectionStrategyMostRecent | This has to match the strategy channel viewers are using. When PhenixStreamSelectionStrategyHighAvailability 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 PhenixPublishToChannelOptions |
Notes: * Wildcard token generation is always enabled when publishing to a channel
PhenixChannelOptionsBuilder
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 PhenixChannelOptions |
Publish To Channel Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation |
roomService | RoomService | Phenix room service |
publisher | PhenixExpressPublisher | Phenix publisher object |
Create a Channel
import PhenixSdk
let channelExpress: PhenixChannelExpress = ... // previously obtained
let channelOptions = PhenixRoomServiceFactory.createChannelOptionsBuilder()
.withName("MyAwesomeChannel")
.withAlias("MyAwesomeChannelAlias") // Not required but if it is provided we will use this as the alias instead
// of pre-generating one for you.
.buildChannelOptions()
channelExpress.createChannel(
channelOptions,
{ (status: PhenixRequestStatus, channel: PhenixImmutableRoom?) in
guard status == .ok else {
// Handle room create error
return
}
// use `channel` to e.g. join
})
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixChannelOptions | 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 | PhenixRequestStatus | The status of the operation. |
channel | PhenixImmutableRoom | Immutable Phenix 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:
- Join, create, and publish to a room
- Subscribe to streams of members in a room
Initializing
import PhenixSdk
let backendEndpointUri = "https://example.yourdomain.com/phenix/"
let pcastExpressOptions = PhenixPCastExpressFactory.createPCastExpressOptionsBuilder()
.withBackendUri(backendEndpointUri)
.buildPCastExpressOptions()
let roomExpressOptions = PhenixRoomExpressFactory.createRoomExpressOptionsBuilder()
.withPCastExpressOptions(pcastExpressOptions)
.buildRoomExpressOptions()
let roomExpress = PhenixRoomExpressFactory.createRoomExpress(roomExpressOptions)
PhenixRoomExpressOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withPCastExpressOptions (required) | PhenixPCastExpressOptions | See PhenixPCastExpressOptionsBuilder | |
buildRoomExpressOptions | <none> | Builds the PhenixRoomExpressOptions |
Join a Room
Join a room and optionally, automatically subscribe to member changes.
import PhenixSdk
let roomExpress: PhenixRoomExpress = ... // previously obtained
let joinRoomOptions = PhenixRoomExpressFactory.createJoinRoomOptionsBuilder()
.withRoomAlias("myRoom42")
.withCapabilities(["streaming"])
.buildJoinRoomOptions()
roomExpress.joinRoom(joinRoomOptions, { (status: PhenixRequestStatus, roomService: PhenixRoomService?) in
if status == .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:
roomExpress.joinRoom(
joinRoomOptions,
{ (status: PhenixRequestStatus, roomService: PhenixRoomService?) in
if status == .ok {
// Hold on to roomService reference for as long as you wish to stay in the room
} else {
// Handle error
}
},
{ (members: [PhenixMember]?) in
// Do something with room members
})
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixJoinRoomOptions | Options to join room with |
joinRoomCallback (required) | Function | Function to call on success/failure of joining the room |
membersChangedCallback (optional) | Function | 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. |
PhenixJoinRoomOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withRoomId (required) | NSString | Id of channel to view | |
withRoomAlias (optional) | NSString | Alias, alternative to ID | |
withCapabilities (required) | NSArray of NSString | The list of all capabilities to subscribe with. | |
withRole (optional) | PhenixMemberRole | PhenixMemberRoleAudience | The Member Role to join with |
withScreenName (optional) | NSString | The member screen name to join with. A random string will be generated if not provided. | |
withStreams (optional) | NSArray of PhenixStream | [] | The member streams to join with. Empty if not provided |
buildJoinRoomOptions | <none> | Builds the PhenixJoinRoomOptions |
Express Join Room Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation. |
roomService | PhenixRoomService | 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 PhenixSdk
let roomExpress: PhenixRoomExpress = ... // previously obtained
let room: PhenixImmutableRoom = ... // previously obtained
let renderLayer: CALayer = ... // previously obtained
// 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.
let member = room.getObservableMembers().getValue()[0] as! PhenixMember
let memberStream = member.getObservableStreams().getValue[0] as! PhenixStream
let options = PhenixRoomExpressFactory.createSubscribeToMemberStreamOptionsBuilder()
.withRenderer(renderLayer)
.buildSubscribeToMemberStreamOptions()
roomExpress.subscribe(
toMemberStream: memberStream,
options,
{ [weak self] (
status: PhenixRequestStatus,
subscriber: PhenixExpressSubscriber?,
renderer: PhenixRenderer?) in
guard status == .ok, let strongSelf = self else {
// Handle subscribe error
return
}
// Important: Store subscriber reference, otherwise we will stop subscription immediately:
strongSelf.currentSubscriber = subscriber
})
Parameters
Name | Type | Description |
---|---|---|
memberStream (required) | PhenixStream | 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 |
PhenixSubscribeToMemberStreamOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withCapabilities (optional) | NSArray of NSString | [] | The list of all capabilities to subscribe with |
withRenderer (optional) | CALayer | <none> | Render layer 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) | PhenixRendererOptions | <none> | Options passed to renderer. Will trigger instantiation of renderer |
withMonitor (optional) | PhenixMonitorSetupFailedCallback, PhenixMonitorStreamEndedCallback, PhenixMonitorOptions | <none> | Options for monitoring a subscriber for failure |
withConnectOptions (optional) | NSArray of NSStrings | [] | List of options for subscribing |
withTags (optional) | NSArray of NSStrings | [] | Tags for the stream |
buildSubscribeToMemberStreamOptions | <none> | Builds the PhenixSubscribeToMemberStreamOptions |
Subscribe To Member Stream Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | 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 PhenixSdk
let roomExpress: PhenixRoomExpress = ... // previously obtained
let renderLayer: CALayer = ... // previously obtained
let mediaConstraints = PhenixUserMediaOptions()
// Customize constraints if needed
let localPublishOptions = PhenixPCastExpressFactory.createPublishOptionsBuilder()
.withCapabilities(["hd", "streaming"])
.withMediaConstraints(mediaConstraints)
.withPreviewRenderer(renderLayer)
.buildPublishOptions()
let roomOptions = PhenixRoomServiceFactory.createRoomOptionsBuilder()
.withName("MyAwesomeRoom")
.buildRoomOptions()
let localPublishToRoomOptions = PhenixRoomExpressFactory.createPublishToRoomOptionsBuilder()
.withStreamType(.user)
.withMemberRole(.participant)
.withRoomOptions(roomOptions)
.withPublishOptions(localPublishOptions)
.buildPublishToRoomOptions()
roomExpress.publish(
toRoom: localPublishToRoomOptions,
withCallback: { [weak self] (
status: PhenixRequestStatus,
roomService: PhenixRoomService?,
publisher: PhenixExpressPublisher?) in
guard status == .ok, let strongSelf = self else {
// Handle publish error
return
}
// Important: Store publisher reference, otherwise we will stop publisher immediately:
strongSelf.currentPublisher = publisher
})
// OR for a remote stream:
let remotePublishOptions = PhenixPCastExpressFactory.createPublishRemoteOptionsBuilder()
.withStreamUri("http://example.com/mystream.mp4")
.buildPublishRemoteOptions()
let remotePublishToRoomOptions = PhenixRoomExpressFactory.createPublishToRoomOptionsBuilder()
.withStreamType(.user)
.withMemberRole(.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 |
PhenixPublishToRoomOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withRoomOptions (required) | PhenixRoomOptions | If omitted, then withRoomId needs to be provided. |
|
withRoomId (required) | NSString | ID of room to publish to. If omitted, then withRoomOptions needs to be provided. |
|
withPublishOptions *(required) * | PhenixPublishOptions | Either local or remote publish options are required | |
withPublishRemoteOptions (required) | PhenixPublishRemoteOptions | Either local or remote publish options are required | |
withMemberRole (required) | PhenixMemberRole | Role of member to join room as (used if not already in room). See Member Roles | |
withStreamType (required) | PhenixStreamType | Type of stream to publish. See Stream Types | |
withScreenName (optional) | NSString | <automatically generated> | Screen name of self member |
withViewerStreamSelectionStrategy (optional) | PhenixStreamSelectionStrategy | PhenixStreamSelectionStrategyMostRecent | 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 PhenixPublishToRoomOptions |
Publish To Room Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | 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 PhenixSdk
let roomExpress: PhenixRoomExpress = ... // previously obtained
let pcastExpress = roomExpress.pcastExpress
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). Once those references as well as any reference to the room express instance itself have been released, 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:
- Publishing local media
- Publishing remote media (ingest)
- 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 PhenixSdk
let pcastExpressOptions = PhenixPCastExpressFactory.createPCastExpressOptionsBuilder()
.withBackendUri("https://example.yourdomain.com/phenix/")
.buildPCastExpressOptions()
let pcastExpress = PhenixPCastExpressFactory.createPCastExpress(pcastExpressOptions)
PhenixPCastExpressOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withBackendUri (required) | NSString | Url to your backend. We send requests here to authenticate and get streaming tokens. | |
withAuthenticationData (optional) | NSString | 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) | NSString | 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) | PhenixPCastExpressUnrecoverableErrorCallback | Function to be called when authentication fails or a failure occurs that is unrecoverable. | |
withPCastUri (optional) | NSString | Allows overriding default PCast URI. | |
withPCastInitializationOptions (optional) | PhenixPCastInitializeOptions | Use custom options when initializing PCast. | |
withAuthenticationRouteOverride (optional) | NSString | auth | Allows override of default route for authentication tokens |
withStreamRouteOverride (optional) | NSString | stream | Allows override of default route for stream tokens |
buildPCastExpressOptions | <none> | Builds the PhenixPCastExpressOptions |
Publishing Local Media
Publish local user media:
- Camera
- Microphone
import PhenixSdk
let pcastExpress: PhenixPCastExpress = ... // previously obtained
let userMediaConstraints = PhenixUserMediaOptions()
userMediaConstraints.video.enabled = true
userMediaConstraints.video.capabilityConstraints[PhenixDeviceCapability.facingMode.rawValue] =
[PhenixDeviceConstraint.initWith(PhenixFacingMode.user)]
userMediaConstraints.audio.enabled = true
userMediaConstraints.audio.capabilityConstraints[PhenixDeviceCapability.audioEchoCancelationMode.rawValue] =
[PhenixDeviceConstraint.initWith(PhenixAudioEchoCancelationMode.on)]
let publishOptions = PhenixPCastExpressFactory.createPublishOptionsBuilder()
.withCapabilities(["real-time"])
.withMediaConstraints(userMediaConstraints)
.buildPublishOptions()
pcastExpress.publish(publishOptions) { (status: PhenixRequestStatus, publisher: PhenixExpressPublisher?) in
if status == .ok {
// Do something with publisher
} else {
// Handle error
}
}
// Create a publisher with an automatically started preview renderer
let renderLayer: CALayer = ... // previously obtained
let publishOptionsWithPreview = PhenixPCastExpressFactory.createPublishOptionsBuilder()
.withCapabilities(["real-time"])
.withMediaConstraints(userMediaConstraints)
.withPreviewRenderer(renderLayer)
.buildPublishOptions()
pcastExpress.publish(withPreview: publishOptionsWithPreview) {
(status: PhenixRequestStatus, publisher: PhenixExpressPublisher?, preview: PhenixRenderer?) in
if status == .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
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixPublishOptions | Publish options |
callback (required) | function | Callback for error/success handling |
PhenixPublishOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withMediaConstraints (required) | PhenixUserMediaOptions | getUserMedia options Constraints to get the user media. | |
withUserMedia (optional) | PhenixUserMediaStream | alternative to withMediaConstraints - you can pass user media stream returned from getUserMedia. |
|
withCapabilities (optional) | NSArray of NSString | The list of all capabilities to publish with. Default is empty array. | |
withPreviewRenderer (optional) | CALayer | 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) | PhenixRendererOptions | Options passed to preview renderer. Will trigger instantiation of preview renderer. | |
withMonitor (optional) | PhenixMonitorSetupFailedCallback, PhenixMonitorStreamEndedCallback, PhenixMonitorOptions | Options for monitoring a publisher for failure. | |
withConnectOptions (optional) | NSArray of NSStrings | List of options for publishing. | |
withTags (optional) | NSArray of NSStrings | Tags for the stream. | |
withStreamToken (optional) | NSString | 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 | <none> | Builds the PhenixPublishOptions |
Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation |
publisher | PhenixExpressPublisher | Phenix publisher object |
previewRenderer | PhenixRenderer | Optionally provided if any of the withPreview... methods were called on the options builder and publish is invoked with withPreview . |
PhenixExpressPublisher
Shares most methods with regular PhenixPublisher returned by PhenixPCast, 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 PhenixStreamEndedReasonCustom 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) | PhenixDisposable | Temporarily limit published video bitrate, see Limit Bitrate |
getStreamId | () | NSString | 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 PhenixSdk
let pcastExpress: PhenixPCastExpress = ... // previously obtained
let publishRemoteOptions = PhenixPCastExpressFactory.createPublishRemoteOptionsBuilder()
.withStreamUri("http://mycdn.example.com/mystream.mp4")
.withCapabilities([""])
.buildPublishRemoteOptions()
pcastExpress.publishRemote(publishRemoteOptions) { (status: PhenixRequestStatus, publisher: PhenixExpressPublisher?) in
if status == .ok {
// Do something with publisher
} else {
// Handle error
}
}
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixPublishRemoteOptions | Publish Remote options |
callback (required) | function | Callback for error/success handling |
PhenixPublishRemoteOptionsBuilder
Name | Type | Default | Description |
---|---|---|---|
withCapabilities (required) | NSArray of NSString | The list of all capabilities to publish with. | |
withStreamUri (required) | NSString | Link to remote media (mp4, rtmp, etc.) | |
withStreamToken (optional) | NSString | 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) | NSArray of NSStrings | List of options for publishing from a remote source. | |
withTags (optional) | NSArray of NSStrings | Tags for the stream | |
withMaximumFrameRateConstraint (optional) | double | Maximum frame rate constraint. | |
withExactFrameRateConstraint (optional) | double | Exact frame rate constraint. | |
withPrerollSkipDuration (optional) | NSTimeInterval | 500 | The amount of time to skip at the beginning of the media. |
buildPublishRemoteOptions | <none> | Builds the PhenixPublishRemoteOptions |
Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation. |
publisher | PhenixExpressPublisher | Phenix publisher object |
Subscribing to Published Media
Subscribe to streams published with the Phenix platform
import PhenixSdk
let pcastExpress: PhenixPCastExpress = ... // previously obtained
let renderLayer: CALayer = ... // previously obtained
let subscribeOptions = PhenixPCastExpressFactory.createSubscribeOptionsBuilder()
.withStreamId("us-west#us-west1-b.zzzzzzzz.20000000.xxxxxxxx")
.withCapabilities(["real-time"])
.withRenderer(renderLayer)
.buildSubscribeOptions()
pcastExpress.subscribe(subscribeOptions) { (status: PhenixRequestStatus, subscriber: PhenixExpressSubscriber?, renderer: PhenixRenderer?) in
if status == .ok {
// Do something with subscriber
if let renderer = renderer {
// 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 PhenixExpressSubscriber 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
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixSubscribeOptions | Subscribe options |
callback (required) | function | Callback for error/success handling |
Subscribe Options
Name | Type | Default | Description |
---|---|---|---|
withStreamId (required) | NSString | The stream ID of the published stream | |
withCapabilities (required) | NSArray of NSString | The list of all capabilities to subscribe with. | |
withStreamToken (optional) | NSString | 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) | CALayer | 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) | PhenixRendererOptions | Options passed to renderer. Will trigger instantiation of renderer. | |
withMonitor (optional) | PhenixMonitorSetupFailedCallback, PhenixMonitorStreamEndedCallback, PhenixMonitorOptions | Options for monitoring a subscriber for failure. | |
withConnectOptions (optional) | NSArray of NSStrings | List of options for subscribing. | |
withTags (optional) | NSArray of NSStrings | Tags for the stream | |
buildSubscribeOptions | <none> | Builds the PhenixSubscribeOptions |
PhenixExpressSubscriber
Shares most methods with regular PhenixMediaStream returned by PhenixPCast, see Subscribe to a Stream.
Name | Signature | Returns | Description |
---|---|---|---|
createRenderer | () | PhenixRenderer | Creates a new renderer. This should only be called if none of the withRenderer... builder methods were invoked. |
createRenderer | (PhenixRendererOptions) | PhenixRenderer | Creates a new renderer with PhenixRendererOptions. This should only be called if none of the withRenderer... builder methods were invoked. |
getAudioTracks | () | NSArray of PhenixMediaStreamTrack | Returns all associated audio tracks of this stream |
getVideoTracks | () | NSArray of PhenixMediaStreamTrack | Returns all associated video tracks of this stream |
getTracks | () | NSArray of PhenixMediaStreamTrack | 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 iOS, 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 PhenixOptionalAction 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 may call dismiss
on the retry action, but you do not have to (the action is automatically dismissed when no longer referenced). You also may defer invoking the retry action.
Example Monitor for subscribing
import PhenixSdk
let monitorOptions = PhenixPCastExpressFactory.createMonitorOptionsBuilder().buildMonitorOptions();
let subscribeOptions = PhenixPCastExpressFactory.createSubscribeOptionsBuilder()
.withStreamId("us-west#us-west1-b.zzzzzzzz.20000000.xxxxxxxx")
.withCapabilities(["real-time"])
.withMonitor({ (status: PhenixRequestStatus, retry: PhenixOptionalAction?) in
// Stream failed to setup, check if retry is a possibility:
if let retry = retry, retry.isPresent() {
if determineWhetherToRetry() {
retry.perform()
} else {
// Not technically necessary, but here for clarity
retry.dismiss()
}
}
},
{ (reason: PhenixStreamEndedReason, description: String?, retry: PhenixOptionalAction?) in
// Stream has ended, check if due to failure
if let retry = retry, retry.isPresent() {
if reason == .failed {
retry.perform()
} else {
// Not technically necessary, but here for clarity
retry.dismiss()
}
}
},
monitorOptions)
.buildSubscribeOptions()
PhenixOptionalAction
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. Dropping reference to PhenixOptionsAction has same effect. |
isPresent | () | bool | Indicates whether an action can be performed. |
PhenixMonitorSetupFailedCallback Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation. |
retry | PhenixOptionalAction | Optionally allow retrying the failed stream. |
PhenixMonitorStreamEndedCallback Callback Arguments
Name | Type | Description |
---|---|---|
reason | PhenixStreamEndedReason | Reason for stream ended. |
description | NSString | Optional additional ended reason description. Carries custom message. |
retry | PhenixOptionalAction | 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 PhenixSdk
let pcastExpress: PhenixPCastExpress = ... // previously obtained
let userMediaOptions = PhenixUserMediaOptions()
pcastExpress.getUserMedia(userMediaOptions) { (status: PhenixRequestStatus, userMedia: PhenixUserMediaStream?) in
if status == .ok {
// Do something with user media stream
} else {
// Handle error
}
}
Parameters
Name | Type | Description |
---|---|---|
options (required) | PhenixUserMediaOptions | User media options |
callback (required) | function | Callback for error/success handling |
Callback Arguments
Name | Type | Description |
---|---|---|
status | PhenixRequestStatus | The status of the operation. |
userMedia | PhenixUserMediaStream | 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 PhenixSdk
let pcastExpress: PhenixPCastExpress = ... // previously obtained
let pcast = pcastExpress.pcast
Clean up
Subscribers and publishers are kept alive for as long as they are being referenced in your app. PhenixPCastExpress will only shutdown once it is no longer being referenced.
Process Raw Frames
The Frame Ready API allows access to raw unencoded audio and video frames on the publisher as well as subscriber side. This enables use cases such as the following:
- Injection of raw frames from a custom source
- Composition of raw frames (e.g. for applying watermarks, or stickers)
- Application of effects (e.g. changing audio volume, applying video filters)
- Controlling playback (e.g. temporary slow-motion, pause)
Please note: Any processing needs to keep up with the incoming frame rate; otherwise some of the incoming frames will be dropped to compensate.
Publisher side
The API is attached to PhenixUserMediaStream
and called setFrameReadyCallback
. A track needs to be passed in to indicate for which frames to receive notifications.
PhenixUserMediaStream
(via its contained PhenixMediaStream
) provides access to the currently available tracks via getVideoTracks
and getAudioTracks
.
Example Code for processing video raw frames
import PhenixSdk
let userMediaStream: PhenixUserMediaStream = ... // previously obtained
// Assume there is at least one video track:
let videoTrack = userMediaStream.mediaStream.getVideoTracks()[0]
// 1) Example showing how read incoming video frame and produce and outgoing one:
userMediaStream.setFrameReadyCallback(videoTrack) { (frameNotification: PhenixFrameNotification?) in
frameNotification?.read { (inputFrame: CMSampleBuffer?) in
let pixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(inputFrame!)!
// Process, manipulate pixel buffer
// ...
// Assume we generate an output CVPixelBuffer, which may or may not be based
// on the incoming frame:
let outputPixelBuffer: CVPixelBuffer = ...
// Assemble an output frame using the same timestamps as the input sample buffer:
let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(inputFrame!)
let duration = CMSampleBufferGetDuration(inputFrame!)
var sampleTimingInfo = CMSampleTimingInfo.init(
duration: duration,
presentationTimeStamp: presentationTimeStamp,
decodeTimeStamp: CMTime.invalid)
var formatDescription: CMFormatDescription? = nil
CMVideoFormatDescriptionCreateForImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: outputPixelBuffer,
formatDescriptionOut: &formatDescription)
var outputFrame: CMSampleBuffer? = nil
CMSampleBufferCreateReadyWithImageBuffer(
allocator: kCFAllocatorDefault,
imageBuffer: outputPixelBuffer,
formatDescription: formatDescription!,
sampleTiming: &sampleTimingInfo,
sampleBufferOut: &outputFrame)
frameNotification?.write(outputFrame)
}
}
// 2) Example showing how we can just directly write output frames from a custom source without the need to
// read the incoming frame:
userMediaStream.setFrameReadyCallback(videoTrack) { (frameNotification: PhenixFrameNotification?) in
// Assume we have a custom source that is able to provide CMSampleBuffer:
let outputFrame: CMSampleBuffer? = ...
frameNotification?.write(outputFrame)
}
// 3) Example showing how we can stop frames from getting propagated by instructing the notification
// to drop them:
userMediaStream.setFrameReadyCallback(videoTrack) { (frameNotification: PhenixFrameNotification?) in
// We want to prevent frames from getting propagated further:
frameNotification?.drop()
}
// 4) Example showing how to read and convert incoming frames to a specific pixel format:
userMediaStream.setFrameReadyCallback(videoTrack) { (frameNotification) in
frameNotification?.read(with: .BGRA) { (inputFrame: CMSampleBuffer?) in
let bgraPixelBuffer: CVPixelBuffer = CMSampleBufferGetImageBuffer(inputFrame!)!
// ...
}
}
Example Code for processing audio raw frames
import PhenixSdk
let userMediaStream: PhenixUserMediaStream = ... // previously obtained
// Assume there is at least one audio track:
let audioTrack = userMediaStream.mediaStream.getAudioTracks()[0]
// 1) Example showing how read incoming audio frame and produce and outgoing one:
userMediaStream.setFrameReadyCallback(audioTrack) { (frameNotification: PhenixFrameNotification?) in
frameNotification?.read { (inputFrame: CMSampleBuffer?) in
let blockBuffer = CMSampleBufferGetDataBuffer(inputFrame!)
var totalLength = Int()
var lengthAtOffset = Int()
var dataPointer: UnsafeMutablePointer<Int8>? = nil
guard CMBlockBufferGetDataPointer(
blockBuffer!,
atOffset: 0,
lengthAtOffsetOut: &lengthAtOffset,
totalLengthOut: &totalLength,
dataPointerOut: &dataPointer) == kCMBlockBufferNoErr && lengthAtOffset == totalLength else {
// Handle error, unexpected buffer length
return
}
// Verify audio format:
guard let formatDescription: CMAudioFormatDescription =
CMSampleBufferGetFormatDescription(inputFrame!) else {
return
}
guard let audioStreamBasicDescription =
CMAudioFormatDescriptionGetStreamBasicDescription(formatDescription) else {
return
}
guard (audioStreamBasicDescription.pointee.mFormatFlags & kAudioFormatFlagIsSignedInteger)
== kAudioFormatFlagIsSignedInteger &&
audioStreamBasicDescription.pointee.mBitsPerChannel == 16 else {
return
}
// Raw samples are contained in `dataPointer` as Int16 values
...
// Assume we generate an output CMBlockBuffer, which may or may not be based
// on the incoming audio frame, and which as the same audio format and same
// length as the incoming frame:
let outputAudioBuffer: CMBlockBuffer = ...
let presentationTimeStamp = CMSampleBufferGetPresentationTimeStamp(inputFrame!)
let numberOfSamples =
totalLength /
Int(audioStreamBasicDescription.pointee.mChannelsPerFrame * audioStreamBasicDescription.pointee.mBitsPerChannel / 8)
var outputFrame: CMSampleBuffer? = nil
CMAudioSampleBufferCreateReadyWithPacketDescriptions(
allocator: kCFAllocatorDefault,
dataBuffer: outputAudioBuffer,
formatDescription: formatDescription,
sampleCount: numberOfSamples,
presentationTimeStamp: presentationTimeStamp,
packetDescriptions: nil,
sampleBufferOut: &outputFrame)
frameNotification?.write(outputFrame)
}
}
// Other examples would look very similar to video
Frame Ready Callback Arguments
This callback gets invoked for each frame that is passing through.
Name | Type | Description |
---|---|---|
frameNotification | PhenixFrameNotification | Object representing the current frame |
Frame Notification
Represents the current frame. Allows for reading, writing, and dropping.
Name | Signature | Returns | Description |
---|---|---|---|
read | (ReadFrameCallback) | void | Retrieves current raw frame in form of a CMSampleBufferRef |
readWithFormat | (PhenixMediaFormat, ReadFrameCallback) | void | Retrieves current raw frame in form of a CMSampleBufferRef |
write | (CMSampleBufferRef) | void | Writes back a processed or newly generated frame. The frame can have a different resolution and timestamps (the buffer attributes are expected to be set correctly) |
drop | () | void | Instructs stream to drop the current frame |
Read Frame Callback Arguments
Receives the current raw frame.
Name | Type | Description |
---|---|---|
frame | CMSampleBufferRef | The raw audio or video frame |
Media Format
Strategy | Description |
---|---|
PhenixMediaFormatI420 | FourCC planar pixel format I420, corresponds to Apple kCVPixelFormatType_420YpCbCr8Planar |
PhenixMediaFormatNV12 | FourCC planar pixel format NV12, corresponds to Apple kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange |
PhenixMediaFormatBGRA | FourCC pixel format ARGB, corresponds to Apple kCVPixelFormatType_32BGRA |
Subscriber side
The Frame Ready API on the subscriber side can be accessed via the renderer, which offers an analoguous setFrameReadyCallback
method to the Publisher Side.
When using the Express API, you can access the stream tracks directly via the Express Subscriber. Otherwise, tracks can be accessed via the PhenixMediaStream
object.
The example code is somewhat abbreviated as the contents of the Frame Ready callbacks would look the same as for the Publisher Side.
Example Code for hooking up frame-ready callback with a PhenixMediaStream object
import PhenixSdk
let mediaStream: PhenixMediaStream = ... // previously obtained
let renderer: PhenixRenderer = ... // previously obtained
// Assume there is at least one video track:
let videoTrack = mediaStream.getVideoTracks()[0]
renderer.setFrameReadyCallback(videoTrack, ...
// Remainder of code identical to Publish Side
Example Code for hooking up frame-ready callback with a PhenixExpressSubscriber object
import PhenixSdk
let expressSubscriber: PhenixExpressSubscriber = ... // previously obtained
let renderer: PhenixRenderer = ... // previously obtained
// Assume there is at least one video track:
let videoTrack = expressSubscriber.getVideoTracks()[0]
renderer.setFrameReadyCallback(videoTrack, ...
// Remainder of code identical to Publish Side
Limit Bandwidth
The outgoing or incoming video bandwidth can be limited on the publisher and subscriber side.
Publisher side
The published video bitrate can be limited temporarily if needed. The returned disposable allows 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.
Example Code for limiting video bandwidth with a PhenixPublisher object
import PhenixSdk
// Previously obtained
let publisher: PhenixPublisher = ...
// Limit video bitrate to 200kbps for 10 seconds
var disposable = publisher.limitBandwidth(200000)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the bandwidth limitation
disposable = nil
}
Example Code for limiting video bandwidth with a PhenixExpressPublisher object
import PhenixSdk
// Previously obtained
let expressPublisher: PhenixExpressPublisher = ...
// Limit video bitrate to 200kbps for 10 seconds
var disposable = expressPublisher.limitBandwidth(200000)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the bandwidth limitation
disposable = nil
}
Parameters
Name | Type | Description |
---|---|---|
bandwidthLimitInBps (required) | UInt64 | Maximum bitrate limit in bps for video |
Subscriber side
Invoking limitBandwidth
on a subscriber will inform the publishing side to lower the video bandwidth to try to match the requested value. The API is attached to PhenixMediaStreamTrack
. The semantics with regards to the disposable returned by the API are identical to the publisher side.
Notes:
- Whether a publisher is able to meet the requested bandwidth also depends on the stream capabilities for the given publisher. For MBR (
multi-bitrate
publisher capability enabled) the publisher will be able to fairly closely match the requested bitrate. With SBR (withoutmulti-bitrate
capability) the bit rate received by the subscriber may not match as well. - The video bitrate will not immediately change after
limitBandwidth
has been invoked. Similarly, once the disposable has been released, it may take several seconds for the bitrate to recover.
Example Code for limiting video bandwidth with a PhenixMediaStream object
import PhenixSdk
// Previously obtained
let subscriber: PhenixMediaStream = ...
// Limit video bitrate to 200kbps for 10 seconds
// We assume there is at least one video track on this stream
var disposable = subscriber.getVideoTracks()[0].limitBandwidth(200000)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the bandwidth limitation
disposable = nil
}
Example Code for limiting video bandwidth with a PhenixExpressSubscriber object
import PhenixSdk
// Previously obtained
let subscriber: PhenixExpressSubscriber = ...
// Limit video bitrate to 200kbps for 10 seconds
// We assume there is at least one video track on this stream
var disposable = subscriber.getVideoTracks()[0].limitBandwidth(200000)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the bandwidth limitation
disposable = nil
}
Parameters
Name | Type | Description |
---|---|---|
bandwidthLimitInBps (required) | UInt64 | Maximum bitrate limit in bps for video |
Override Playout Delay
Example Code for overriding the playout delay via a PhenixMediaStream object
import PhenixSdk
class SomeClass {
private var currentRendererPlayoutDelayOverride: PhenixDisposable?
private var currentRenderer: PhenixRenderer?
private func setPlayoutDelayOverrideFor10Seconds() {
// Previously obtained
let mediaStream: PhenixMediaStream = ...
self.currentRenderer = mediaStream.createRenderer()
// Override playout delay to 900ms for 10 seconds
let playoutDelay: TimeInterval = 0.9
self.currentRendererPlayoutDelayOverride = self.currentRenderer?.overridePlayoutDelay(playoutDelay)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the playout delay override
self.currentRendererPlayoutDelayOverride = nil
}
}
}
Example Code for limiting video bandwidth with a PhenixExpressSubscriber object
import PhenixSdk
class SomeClass {
private var currentRendererPlayoutDelayOverride: PhenixDisposable?
private var currentRenderer: PhenixRenderer?
private func setPlayoutDelayOverrideFor10Seconds() {
// Previously obtained
let subscriber: PhenixExpressSubscriber = ...
self.currentRenderer = subscriber.createRenderer()
// Override playout delay to 900ms for 10 seconds
let playoutDelay: TimeInterval = 0.9
self.currentRendererPlayoutDelayOverride = self.currentRenderer?.overridePlayoutDelay(playoutDelay)
DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
// Dropping the disposable will undo the playout delay override
self.currentRendererPlayoutDelayOverride = nil
}
}
}
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 PhenixRenderer
is needed. It can be obtained either by creating it from PhenixMediaStream
or PhenixExpressSubscriber
, 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:
- The override represents an absolute value, not a delta.
- If an override increases the playout delay, it will result in content being paused. Example: changing a delay of 1 second to 5 seconds will cause the content to be paused for roughly 4 seconds.
- If an override decreases the playout delay, it will cause a jump where some of the content will be skipped.
- Very large override values will increase the amount of memory consumed. It is generally recommended to stay below 10 seconds.
Parameters
Name | Type | Description |
---|---|---|
desiredPlayoutDelay (required) | TimeInterval | Desired playout delay |
Returns
Type | Description |
---|---|
PhenixDisposable | Ensures override is kept in effect for as long as a strong reference is held |
iOS Examples
You can find our iOS examples on GitHub
Frame Ready API
A simple yet powerful example that composes a camera feed with an external video source in real-time!
WebView
A simple example that integrates WebView to load a web page using the Phenix WebSDK.