Twilio Player SDK for iOS

About

The Twilio Player iOS SDK lets you play a stream in your native iOS application.

We want your feedback! Email video-product@twilio.com with suggested improvements, feature requests and general feedback, or feel free to open a GitHub issue. If you need technical support, contact help@twilio.com.

Installation

The SDK can be integrated either manually, or via Swift Package Manager or CocoaPods. Please see more detailed installation instructions here.

Usage

The following snippets provide demonstrations on how to use the Player iOS SDK in an app. Get up and running with our demo app. For more details about streaming content, visit the Programmable Media documentation.

Connecting a Player and Handling Events

You can connect to a stream with an Access Token using the Player.connect(accessToken:delegate:) API. The connect API returns a Player object in connecting state. You can receive the Player events by passing a PlayerDelegate to the connect API.

AVAudioSession Configuration

Before connecting to a stream, set the AVAudioSession.category to playback and activate the AVAudioSession.

do {
    try AVAudioSession.sharedInstance().setCategory(.playback)
    try AVAudioSession.sharedInstance().setActive(true)
} catch {
    print("‼️ Could not setup AVAudioSession: \(error)")
}

Connect to a stream

Once your app acquires a Twilio access token, you can connect to a stream.


let player = Player.connect(accessToken: "access token", delegate: self)

extension ViewController: PlayerDelegate {

    func playerDidChangePlayerState(player: Player, playerState state: Player.State) {
        XCTAssertEqual(player.state, state)
        switch state {
        case .connecting:
                /**
                 * The player is connecting to to Twilio Media Service.
                 */
            break

        case .idle:
                /**
                 * The player has successfully authenticated and is loading the stream. This
                 * state is also reached as a result of calling player.pause().
                 */
            break

        case .ready:
                /**
                 * The player is ready to playback the stream.
                 */
            break

        case .buffering:
                /**
                 * The player is buffering content.
                 */
            break

        case .playing:
                /**
                 * The player is now playing a stream. This state occurs as a result of calling
                 * player.play().
                 */
            break

        case .ended:
                /**
                 * The stream has ended.
                 */
            break

        default:
            break
        }
    }

    func playerDidFailWithError(player: Player, error: Error) {
         /**
         * An error occurred connecting the player or loading a stream.
         */
    }
}

Managing Playback

The following snippets demonstrate how to manage playback in your app.


/**
 * Once the Player reaches the `.ready` state after initially calling 
 * Player.connect(...), an application can invoke play(). The player will then transition to 
 * the `.playing` state once playback successfully begins.
 * 
 * play() can also be called during the `.idle` state only after transitioning as a result
 * of calling pause().
 */
player.play()

/**
 * To pause playback, invoke pause(). The player will transition to `.idle`
 * until playback is resumed with a subsequent call to play().
 */
player.pause()

/**
 * Playback audio can be muted by updating the muted property.
 */
player.muted = true

/**
 * The playback volume can also be adjusted. Updating the muted property has no effect
 * on the volume property. Once a stream is un-muted, playback will ensue with the last set
 * player volume.
 */
player.volume = 0.25

Using TimedMetadata

When your application inserts TimedMetadata into a stream, the player will emit the playerDidReceiveTimedMetadata callback if the player is in the .playing state. TimedMetadata will not be received if the player is paused and any data inserted while a player is paused will not be received once the player is resumed.


func playerDidReceiveTimedMetadata(player: Player, metadata: TimedMetadata) {
    print("Received time metadata = \(metadata)")
}

Playing a stream in background

When the SDK is playing a stream, the Player SDK will continue playing audio when the app backgrounded if the app’s .plist file supports audio background mode.

<key>UIBackgroundModes</key>
<array>
    <string>audio</string>
</array>

If you are rendering a stream using PlayerView, to continue audio in when the app is backgrounded you should reset player.playerView when the app goes to background.

@objc private func applicationDidEnterBackground(notification: Notification) {
    player.playerView = nil
}

@objc private func applicationDidBecomeActive(notification: Notification) {
    player.playerView = playerView
}

Telemetry

The Telemetry API enables applications to subscribe to event and metric data collected by the Player SDK. Telemetry data emitted by the Player SDK can be used to track stream quality, triage issues, and better understand your application’s Player SDK usage.

Typed Data Events

The Player SDK represents telemetry data as strongly typed classes. Reference the table below for a summary of the currently reported events.

Telemetry Data Reporting Details
TelemetryDataConnectionConnecting Reported when Player.connect(...) is called
TelemetryDataConnectionConnected Reported when the Player has a playback token
TelemetryDataConnectionNetworkUnavailable Reported when playerNetworkDidBecomeUnavailable is invoked
TelemetryDataConnectionDisconnected Reported when is Player.dealloc() is called
TelemetryDataConnectionError Reported when playerDidFailWithError is invoked in the .connecting state
TelemetryDataPlaybackPlayed Reported when player.play() is called
TelemetryDataPlaybackPaused Reported when player.pause() is called
TelemetryDataPlaybackVolumeSet Reported when player.volume is set
TelemetryDataPlaybackMuted Reported when player.muted is set to true
TelemetryDataPlaybackUnmuted Reported when player.muted is set to false
TelemetryDataPlaybackError Reported when playerDidFailWithError is invoked in the following states: .idle, .ready, .buffering, .playing
TelemetryDataPlaybackRebuffering Reported when playerWillRebuffer is invoked
TelemetryDataPlaybackQualitySummary Reported every three seconds while the player is .buffering or .playing state
TelemetryDataPlaybackQualitySet Reported when player.quality is set
TelemetryDataPlaybackQualityChanged Reported when playerdidChangeQuality is invoked
TelemetryDataPlaybackQualityVideoSizeChanged Reported when playerVideoSizeChanged is invoked
TelemetryDataPlaybackQualityHighLatencyReductionApplied Reported when the SDK applies the low latency reduction strategy.
TelemetryDataPlaybackQualityHighLatencyReductionReverted Reported when the SDK reverts the low latency reduction strategy.
TelemetryDataPlaybackStateChanged
TelemetryDataTimedMetadataReceived Reported when playerDidReceiveTimedMetadata is invoked
Usage

The following snippets demonstrate a few examples of how to use the Telemetry API.

Telemetry Logger

The following snippet demonstrates the simplest Telemetry example: Logging all Telemetry events emitted by the Player SDK

let log: OSLog = OSLog.init(subsystem: "com.example.logger", category: "CodeSnippet")

// Subscribe the logger to events
Player.telemetry.subscribe(subscriber)

extension Subscriber: TelemetrySubscriber {
    func didReceiveTelemetryData(_ data: TelemetryData) {
        os_log("TELEMETRY : %@", log: log,  type:.default, "\(data)")
    }
}

// .. When your app no longer wants telemetry data, unsubscribe
Player.telemetry.unsubscribe(subscriber)

Track High Latency

The following snippet demonstrates an example of checking experience quality by tracking any instances of high latency.


Player.telemetry.subscribe(subscriber, predicate: { data in
    if let summary = data as? TelemetryDataPlaybackQualitySummary {
        return summary.playerLiveLatency.value > 3_000_000
    }
    return false;
})

extension Subscriber: TelemetrySubscriber {
    func didReceiveTelemetryData(_ data: TelemetryData) {
        yourAppAnalytics.track("high-latency-detected")
    }
}

Track Connection Errors

The following snippet demonstrates an example of using TelemetryPredicate‘s to filter on specific Telemetry events.


Player.telemetry.subscribe(subscriber, predicate: { data in
    return data is TelemetryDataConnectionError 
})

extension Subscriber: TelemetrySubscriber {
    func didReceiveTelemetryData(_ data: TelemetryData) {
        yourAppAnalytics.track("connection-error-detected")
    }
}

Threading Contract

The Player iOS SDK does not provide thread safety. Developers should use a Player instance from the main thread. All callbacks will be emitted on the main thread.

Need help?

Email help@twilio.com, or open an issue in our GitHub Repository and we’ll give you a hand.