iOS Examples - PhenixRTS Docs
iOS Examples
GitHub Examples
You can find our iOS examples on GitHub:
Override Playout Delay
Example Code for overriding the playout delay via a PhenixMediaStream object
Copy
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
Copy
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)
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](/content/docs/sdk_ref/ios/express-channel/#view-a-channel/index.html), [subscribeToMemberStream](/content/docs/sdk_ref/ios/express-room/#subscribe-to-a-members-stream/index.html), [subscribe](/content/docs/sdk_ref/ios/express-pcast/#subscribing-to-published-media/index.html)).
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 |
## Picture-in-picture
Once subscribed to a stream, the renderer may provide an `AVPictureInPictureControllerContentSource` that can be used to initialize an `AVPictureInPictureController` that will show the content of the stream in a picture-in-picture overlay when the application is minimized.
The PhenixRenderer has a `setPictureInPictureContentSourceChangedCallback` function to register for the availability of a `ContentSource` object to use in `Picture-in-Picture` controllers. Note: `setPictureInPictureContentSourceChangedCallback` must be called on the **main thread**.
The `setPictureInPictureContentSourceChangedCallback` method of the renderer provides the `AVPictureInPictureControllerContentSource` object when it is available. If the current renderer does not support picture-in-picture (for example, if it is an audio-only stream), the callback is triggered with a `nil` object.
The example code below configures an `AVPictureInPictureController` to automatically show a picture-in-picture overlay when the application is minimized.
Copy
class SomeClass {
private var pipController: AVPictureInPictureController? private var renderer: PhenixRenderer? public func subscribeToStream() {
// Subscribe to a stream. ...
// After subscription, save the renderer. renderer = ...
// The picture-in-picture controller must be initialized on the main dispatch queue. DispatchQueue.main.async { [weak self] in self?.setupPictureInPictureOverlay() } }
public func setupPictureInPictureOverlay() {
// Ensure this method is called from the main dispatch queue. precondition(Thread.isMainThread, "Picture-in-picture must be initialized from the main thread")
// Showing a Phenix stream in picture-in-picture is supported only starting iOS 15. guard #available(iOS 15, *) else { return }
// Register a callback to update the controller whenever the content source changes. renderer?.setPictureInPictureContentSourceChangedCallback { [weak self] pipContentSource in guard let self = self else { return }
// If the current stream does not support picture-in-picture, the content source is nil. guard let pipContentSource = pipContentSource else { return }
// When subscribing to a new stream, the controller of the previous stream can be used by just // replacing the existing content source. // This allows a seamless transition between two streams in the picture-in-picture overlay. if let pipController = pipController { pipController.contentSource = pipContentSource } else { pipController = AVPictureInPictureController(contentSource: pipContentSource) pipController?.delegate = ... // Optional: use a delegate to catch picture-in-picture events. } } } }
## Set Camera Focus Position
See also [Focus Modes](/content/docs/sdk_ref/ios/pcast-beta/#autofocus-mode/index.html) and [Focus Target](/content/docs/sdk_ref/ios/pcast-beta/#autofocus-target/index.html).
When publishing from the device camera, an application can configure the focal length.
To set an area for the camera to focus on, a focus target is configured. A focus target is expressed in video source frame coordinates.
The video source coordinates can be obtained by calling `PhenixRenderer.convertRenderPreviewCoordinatesToSourceCoordinates` with the renderer currently showing the video preview.
A focus target is specified in relative frame coordinates, which range from 0 to 1 on both the X and Y axis. For example, a (x, y) coordinate focus target of (0.5, 0.5) would specify the camera to focus in the middle of the captured source frame.
If the focus mode is `Continuous` and a focus target has been set, the focus will be adjusted automatically if the device is moved. Note that `Continuous` is the default focus mode, it is not necessary to set it explicitly.
If the focus mode is `AutoThenLocked` and a focus target has been set, the focus distance will not change. If the device is moved, the focus subject will go out of focus and the application will need to adjust the focus again.
If the focus mode is `Locked`, the focus is configured automatically by the device and cannot be changed.
Set the focus target by updating the DeviceCapabilities of the UserMedia used in the application.
Copy
// X and Y must be the coordinates in pixels relative to the surface being rendered.
let locationInView = // CGPoint obtained from touch event handler
// Make relative coordinates using the current renderer. let relativeCoordinates = renderer.convertRenderPreviewCoordinatesToSourceCoordinates(locationInView)
// Update the current user media options // It is recommended to make a copy of the options. This way, the focus can be reset easily by restoring the previous options. let userMediaOptions = // Copy of the currently active options
userMediaOptions.video.capabilityConstraints[PhenixDeviceCapability.autoFocusTarget.rawValue] = [PhenixDeviceConstraint.initWith(relativeCoordinates)]
// Apply the options let status = userMediaStream.applyOptions(userMediaOptions)
// Verify the status // Setting a focus target can fail for example when: // - The current focus mode does not support setting a focus target. // - The current video capture device does not support setting a focus target (front cameras usually do not support it). // - The coordinates are invalid (e.g. outside of the render surface). if (status == .ok) {
// Show a visual indication of where the focus was set. }