This article details how to integrate CDN Mesh Delivery into your mobile app using Kaltura PlayKit on iOS and tvOS
Not into tutorials? |
Kaltura PlayKit
The Kaltura Mobile Video SDK for iOS is a mobile media player that allows to embed Kaltura Video Player into native environments in your iOS or tvOS application.
To learn more about the Kaltura Player SDK, please refer to their documentation.
Project setup
CocoaPods
Note
Cocoapods 1.6.0 + is recommended
To integrate StreamrootSDK and the custom Playkit into your Xcode project using CocoaPods, specify this in your Podfile
:
use_frameworks!
target ' <Your Target Name >'
pod 'StreamrootSDK', '~> 3.23.0'
pod 'PlayKit', '~> 3.16.0'
end
Then, run the following command:
$ pod update
Finally, open the generated Workspace.
Integrate Streamroot SDK
1. Import the SDK
import StreamrootSDK
2. Set the streamrootKey
In the Project Navigator, right click on the main target "Info.plist", and "Open as" → "Source Code".
Add the following lines with the values of the right parameters:
<key>Streamroot</key>
<dict>
<key>Key</key>
<string>streamrootKey</string>
</dict>
Here, <streamrootKey>
refers to your Streamroot key that you will find in the Account section of the Streamroot Dashboard. If you don't have a Streamroot Key, you can register for a free trial account on our website.
3. Declare the dnaClient as an Instance Variable
var dnaClient: DNAClient?
4. Build and start the DNAClient
do {
dnaClient = try DNAClient.builder()
.dnaClientDelegate(self)
.streamrootKey(<#streamrootKey#>)
.start(manifestUrl)
} catch let error {
print("\(error)")
}
Parameter name | Mandatory | Description |
---|---|---|
manifestUrl | Yes | The HLS .m3u8 master playlist URL needsed to be passed to the dnaClient |
latency | Yes | The delay between the playback time and the live edge. To get the best offload capacity, we recommend setting 30s. |
streamrootKey | Yes | The unique Streamroot key that we have assigned to you; make sure it is identical to the one provided in the Account section of your dashboard. |
Other DNA options can be called in the builder. Please refer to this article to learn more about them.
5. Play the stream
guard let localPath = self.dnaClient?.manifestLocalURLPath,
let localUrl = URL(string: localPath) else {
print("Could not properly build final URL with Streamroot SDK")
return
}
// create media source and initialize a media entry with that source
let source = PKMediaSource(entryId, contentUrl: localUrl, drmData: nil, mediaFormat: .hls)
// setup media entry
let mediaEntry = PKMediaEntry(entryId, sources: [source], duration: -1)
// create media config
let mediaConfig = MediaConfig(mediaEntry: mediaEntry)
// prepare the player
self.player!.prepare(mediaConfig)
self.player!.play()
localUrl
refers to the HLS .m3u8
master playlist URL that will be passed to the player.
In order to improve the DNA traffic offload for Live streaming, we highly recommend setting different buffer levels for each player.
Here, at player instantiation, we will distribute the buffer target between 10 and the latency set above (30s recommended).
The maximum buffer target will then dynamically evolve for greater DNA delivery efficiency (Only from SDK 3.9.0).
6. Implement DNAClientDelegate
It is first needed to add the DNAClientDelegate
in an extension.
extension CurrentPlayerViewController: DNAClientDelegate {
func updatePeakBitRate(_ bitRate: Double) {
player.settings.network.preferredPeakBitRate = bitRate
}
func playbackTime() -> Double {
return player.currentTime
}
func loadedTimeRanges() -> [NSValue] {
return (player.loadedTimeRanges ?? [])
.map { NSValue(timeRange: TimeRange(start: $0.start, duration: $0.duration)) }
}
func setBufferTarget(_ target: Double) {
if #available(iOS 10.0, tvOS 10.0, *) {
player.settings.preferredForwardBufferDuration = target
}
}
func bufferTarget() -> Double {
if #available(iOS 10.0, tvOS 10.0, *) {
return player.settings.preferredForwardBufferDuration
}
return 0.0
}
}
Method name | Mandatory | Description |
---|---|---|
playbackTime | Yes | Must return the current time of the player in seconds. |
loadedTimeRanges | Yes | Must return an array of NSValue. Each NSValue must contain a TimeRange object (in seconds). |
bitrate | Yes | Must be implemented to update the player with the new bitrate. |
bufferTarget | No | Should be implemented to update the player with dynamic buffer target. |
7. Integrate the QoS module (optional)
This QoS module allows you to get some Kaltura PlayKit events and link them to our SDK in order to get accurate metrics regarding the client session.
If this QoS module isn't implemented, a default module directly embedded in the SDK will be used instead.
class PlayKitQoSModule {
let dnaQoSModule: QosModule = DNAQos.module(type: .custom)
public init(_ player: Player) {
player.addObserver(self, event: PlayerEvent.stateChanged, block: { event in
switch (event.newState) {
case .idle:
self.dnaQoSModule.playerStateDidChange(PlaybackState.idle)
case .ready:
player.isPlaying
? self.dnaQoSModule.playerStateDidChange(PlaybackState.playing)
: self.dnaQoSModule.playerStateDidChange(PlaybackState.paused)
case .buffering:
self.dnaQoSModule.playerStateDidChange(PlaybackState.buffering)
case .ended:
self.dnaQoSModule.playerStateDidChange(PlaybackState.ended)
case .error:
self.dnaQoSModule.playbackErrorOccurred()
default:
break
}
})
player.addObserver(self, event: PlayerEvent.videoTrackChanged, block: { _ in self.dnaQoSModule.trackSwitchOccurred() })
player.addObserver(self, event: PlayerEvent.seeked, block: { _ in self.dnaQoSModule.playerStateDidChange(PlaybackState.seeking) })
player.addObserver(self, event: PlayerEvent.playbackStalled, block: { _ in self.dnaQoSModule.playbackErrorOccurred() })
player.addObserver(self, event: PlayerEvent.ended, block: { _ in self.dnaQoSModule.playerStateDidChange(PlaybackState.ended) })
}
}
It is then required to call the QosModule in the builder.
do {
dnaClient = try DNAClient.builder()
.dnaClientDelegate(self)
.streamrootKey(<#streamrootKey#>)
.qosModule(qosModule)
.start(manifestUrl)
} catch let error {
print("\(error)")
}
Stop the SDK
override func viewDidDisappear(_ animated: Bool) {
dnaClient?.stop()
}
Disable App Transport security
In the Project Navigator, right click on "Info.plist", and "Open as" → "Source Code".
Add the following lines with the corresponding parameters values.
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Integrating with the OVPMediaProvider
If you are using OVP content you will need to get the media using the OVPMediaProvider
to remotely get your media. More information can be found in Kaltura's documentation.
This step must be done before creating the Mesh Delivery Client instance explained in Step 2.
You need to pass the media URL retrieved from the OVPMediaProvider
to the SDK and then retrieve the localUrl
to pass it to your player.
Hereunder is an example on how to do so.
func loadMedia() {
let sessionProvider = SimpleSessionProvider(serverURL: SERVER_BASE_URL, partnerId: Int64(PARTNER_ID), ks: ks)
let mediaProvider: OVPMediaProvider = OVPMediaProvider(sessionProvider)
mediaProvider.entryId = entryId
mediaProvider.loadMedia { mediaEntry, error in
if let me = mediaEntry, error == nil {
for source in me.sources! {
if source.mediaFormat.rawValue == MediaFormat.hls.rawValue {
source.contentUrl = self.loadStreamroot(source: source)
break
}
}
// create media config
let mediaConfig = MediaConfig(mediaEntry: me, startTime: 0.0)
// Update Kava config
self.player.updatePluginConfig(pluginName: KavaPlugin.pluginName, config: self.createKavaConfig())
// prepare the player
self.player.prepare(mediaConfig)
self.state = .paused
self.playPauseButton.isEnabled = true
self.playheadSlider.isEnabled = (me.mediaType != .live)
} else {
self.playPauseButton.isEnabled = false
let alertController = UIAlertController(title: "An error has occurred",
message: "The media could not be loaded",
preferredStyle: UIAlertController.Style.alert)
alertController.addAction(UIAlertAction(title: "try again", style: UIAlertAction.Style.cancel, handler: { _ in
self.loadMedia()
}))
self.show(alertController, sender: self)
}
}
}
Then, it is recommended to also pass the content Id in the builder using the mediaId fetched from the PKMediaSource.
dnaClient = try DNAClient.builder()
.dnaClientDelegate(self)
...
.contentId(source.id)
...
.start(manifestUrl)
} catch let error {
print("\(error)")
}
A full sample application can be found here.
Integrating with the PhoenixMediaProvider
If you are using OTT content you will need to get the media using the PhoenixMediaProvider
to remotely get your media. More information can be found in Kaltura's documentation.
This step must be done before creating the Mesh Delivery Client instance explained in Step 2.
You need to pass the media URL retrieved from the PhoenixMediaProvider
to the SDK and then retrieve the localUrl
to pass it to your player.
Hereunder is an example on how to do so.
/************************/
// MARK: - Player Setup
fileprivate func updateMediaEntryWithLocalUrl(_ mediaEntry: PKMediaEntry) {
for source in mediaEntry.sources! where source.mediaFormat.rawValue == MediaFormat.hls.rawValue{
source.contentUrl = self.loadStreamroot(url: source.contentUrl!, withId: source.id)
break
}
}
/***********************/
func preparePlayer() {
// Setup the player's view
self.player?.view = self.playerContainer
// Create a session provider
let sessionProvider = SimpleSessionProvider(serverURL: SERVER_BASE_URL, partnerId: Int64(PARTNER_ID), ks: nil)
// Create the media provider
let phoenixMediaProvider = PhoenixMediaProvider()
phoenixMediaProvider.set(assetId: ENTRY_ID)
phoenixMediaProvider.set(type: ASSET_TYPE)
phoenixMediaProvider.set(formats: [FORMATS])
phoenixMediaProvider.set(playbackContextType: PLAYBACK_CONTEXT_TYPE)
phoenixMediaProvider.set(sessionProvider: sessionProvider)
phoenixMediaProvider.loadMedia { (pkMediaEntry, error) in
guard let mediaEntry = pkMediaEntry else { return }
self.updateMediaEntryWithLocalUrl(mediaEntry)
// Create media config
let mediaConfig = MediaConfig(mediaEntry: mediaEntry)
// Prepare the player
self.player?.prepare(mediaConfig)
self.playheadSlider.isEnabled = mediaEntry.mediaType != .live
}
}
Then, it is recommended to also pass the content Id in the builder using the mediaId fetched from the PKMediaSource.
dnaClient = try DNAClient.builder()
.dnaClientDelegate(self)
...
.contentId(source.id)
...
.start(manifestUrl)
} catch let error {
print("\(error)")
}
A full sample application can be found here.