To integrate the Mesh Delivery SDK, we need:
- A valid Delivery Client Key (formerly Streamroot Key). It is available in the Account section of your dashboard.
- The Mesh Delivery SDK Framework installed.
NOTE: For this sample app, we are using demoswebsiteandpartners
Delivery Client Key. If you do not have one, you can ask for a free trial on our website. In the following tutorial, every mention to the Delivery Client Key will use the <delivery-client-key>
placeholder.
Not into Tutorials? Take a look at our sample app
The Mesh Delivery SDK is delivered as an Xcode framework and is available on Cocoapods.
To get the SDK via cocoapods, add pod 'LumenMeshSDK'
to your podfile like this:
target 'MyApp' do
use_frameworks!
pod 'LumenMeshSDK'
end
Then, execute pod install
In the Project Navigator, right click on "Info.plist", and "Open as" → "Source Code". Add the following lines with the right parameters values.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
In the Project Navigator, right click on "Info.plist", and "Open as" → "Source Code". Add the following lines with the right parameters values.
<key>DeliveryClient</key>
<dict>
<key>Key</key>
<string><delivery-client-key></string>
</dict>
We strongly recommend to set the Delivery Client Key in Info.plist
. However, if not possible, it is also possible to pass it during the initialization step.
First, import the SDK:
import LumenMeshSDK
Initialize the Mesh Delivery SDK from the AppDelegate
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
/*
* If you can not add your Delivery Client Key in Info.plist
* Call instead: LMDeliveryClient.initializeApp(withDeliveryKey: "<delivery-client-key>")
*/
LMDeliveryClient.initializeApp()
...
}
...
}
In order to work correctly, the SDK instance uses a PlayerInteractor
.
It is the component in charge of the interactions between the player and the SDK. It monitors Quality of Service (QoS) metrics and allows the SDK to behave accordingly.
To feed the SDK properly, this class needs to inherit from LMPlayerInteractorBase
and follow these requirements:
- The
super.playbackErrorOccurred()
method must be called whenever an error occurs. - The
super.updateDroppedFrameCount()
method must be called with the new number of frame dropped. - The
playerStateDidChange(newState)
method must be notified of each player state change. - The
trackSwitchOccurred()
method must be called when the player changes the current track. - The
playbackTime() -> Double
method should return the current playback time since the beginning in milliseconds. - The
bufferHealth() -> Double
should return the duration it is possible to play starting from the playback position using what has been buffered. - The
bufferTarget() 0> Double
should return the target duration the player tries to have buffered at any time. - The
setBufferTarget(target: Double)
should set the target duration the player should try to have buffered at any time. - The
setEstimatedBandwidth(bps: NSNumber?)
should force the bandwidth estimate of the player to the given value.
When integrating the SDK, you are free to implement this component but we provide an implementation example for AVPlayer in PlayerInteractor.swift.
Now that the SDK is initialized, you are able to create LMDeliveryClient
instances. It can be configured pretty easily as such:
func createDeliveryClient() -> LMDeliveryClient {
return LMDeliveryClientBuilder.clientBuilder()
/*
* Set the player interactor to use
* Check the bridge section to know more
*
* param: an instance of a class subclassing LMPlayerInteractorBase.
*/
.playerInteractor(PlayerInteractor())
/*
* Build a LumenDeliveryClient instance
*
* param: String. The video stream url
*/
.build(url)
}
Note: PlayerInteractor
is referencing the bridge class from step 2, depending of your implementation the naming can differ from our example, but, it must subclass LMPlayerInteractorBase
Calling start()
on the LMDeliveryClient
instance will start the SDK.
Once you have a running instance of the SDK, you must retrieve the final URL and input it to your player instead of your original one.
var deliveryClient = createDeliveryClient()
deliveryClient.start();
guard let deliveryUrl = deliveryClient.localManifestURL else {
fatalError("Local url manifest could not be generated")
}
let playerItem = AVPlayerItem(asset: AVURLAsset(url: deliveryUrl))
Start the player with the new url provided by the LMDeliveryClient
then link it with the PlayerInteractor
:
let player = AVPlayer(playerItem: playerItem)
playerInteractor.linkPlayer(player!, playerItem: playerItem)
Note: The interactor is linked with a specific player AND a specific player item. The same object should be used inside AVPlayer and inside the linkPlayer() method.
To play the stream, simply do:
player?.play()
Make sure to stop the LMDeliveryClient
once you are done with the video. We recommend to put it in the viewDidDisappear(:bool)
or any callback terminating the player lifecycle.
self.deliveryClient.stop()
You can pass additional options during the creation of a LMDeliveryClient
func createDeliveryClient() -> LMDeliveryClient {
return LMDeliveryClientBuilder.clientBuilder()
/*
* Set the player interactor to use
* Check the bridge section to know more
*
* param: an instance of a class subclassing LMPlayerInteractorBase.
*/
.playerInteractor(PlayerInteractor())
/*
* Set Mesh property
*
* param: String
*/
.meshProperty("MY_PROPERTY")
/*
* Set the Delivery Client Key
* Is only required if it was not set in Info.plist
* Will override the Info.plist DeliveryClientKey value
*
* param: String
*/
.deliveryClientKey("<delivery-client-key>")
/*
* Set the content id
* A string that identifies your content
* By default, it uses the stream url
*
* param: String
*/
.contentId("MY_CONTENT_ID")
/*
* Set the log level
* See the "How to investigate?" to know more
*
* param: LumenLogLevel
*/
.logLevel(.info)
/*
* Set latency in seconds
*
* param: Int
*/
.latency(30)
/*
* Set a proxy server
* Allows the use of a proxy server in the middle
* Format is host:port
*
* params: String
*/
.proxyServer("MY_PROXY_HOST:PORT")
/*
* Build a LumenDeliveryClient instance
*
* param: String. The video stream url
*/
.build(url)
}
By default the log level is set to OFF
for initalization, it can be turned on when building an instance of LMDeliveryClient
:
func createDeliveryClient() -> LMDeliveryClient {
return LMDeliveryClientBuilder.clientBuilder()
.playerInteractor(PlayerInteractor())
.logLevel(.warning)
.build(url)
}
Notes:
- Valid value for
LMLogLevel
aretrace
,critical
,error
,warning
,info
,debug
oroff
.
A helper method is available to display various Mesh Delivery related stats on a specified UIView.
// The implementer is in charge to create the view and to display it on top of the player controller/layer
self.deliveryClient.displayStatWiew(someView!)
Note: This sample app is using AVPlayerViewController, on iOS we are adding the view as a subview of AVPlayerViewController
. On tvOS, we suggest to use customOverlayViewController instead.
The SDK is player agnostic. All communication between the player and the delivery client that are player specific are implemented in a PlayerInteractor class. Each player has a different API that the SDK tries to use at its full potential in order to monitor and maximize the Quality of Service. The lack of some interfaces may :
- Reduce QoS detection
- Reduce offload
States
- INVALID : Unused
- IDLE : OK
- PLAYING : OK
- PAUSED : OK
- SEEKING : OK
- REBUFFERING : OK
- ENDED : OK
Misc
- Playback time : OK
- Bandwidth control : OK
- Buffer health : OK
- Track switch : Experimental
- Player error : OK
- Frame drop : OK
- Set buffer target : OK
- Get buffer target : OK
We currently do not support P2P in Airplay casting mode