Overview
The following article will explain all breaking changes, renamed functions and configuration objects, that need to be fulfilled to migrate and benefit from all the major performance and usability improvements that become available with our new iOS/tvOS SDK 22.X
The new iOS/tvOS SDK 22.X reflect our Lumen transition as well many rework of our internal modules architecture.
We have put a lot of effort to make sure the integration remains as simple as possible and as close to what we had in the previous versions.
Most of the changes concerns renaming from "streamroot" to "lumen" and from "DNA" to "MESH".
The complete integration steps to follow for a brand new integration can be found at AVplayer from SDK 22.01.
In summary
Footprint
Compared to previous versions:
- 2x less average user CPU time
- 4x less average RAM usage, runtime is ~17MB (plus configurable P2P cache size, by default 60MB for live and 80MB for VOD)
- Lower device hardware requirement compatibility (e.g. 4cores CPU, 1GB RAM)
SDK size
- Total bundle 14-17MB (final size depends on the device CPU architecture)
Features
- Increased CDN Offload: expect +5-20% of P2P offload
- Reduced footprint on low-end devices
- Improved reaction to network speed changes
- Additional client side debugging tools, via Stats View external library
Versioning scheme
The SDK now uses a date-based versioning scheme.
It is based on Year, Month, Patch as follows: YY.MM.patch
Dependency
The full dependency of the SDK changed from StreamrootSDK
to LumenMeshSDK
.
It is now declared as follows:
Before 22.01
With Cocoapods
use_frameworks!
target ' <Your Target Name >'
pod 'StreamrootSDK', '~> 3.25.1'
end
With Carthage
binary "https://sdk.streamroot.io/ios/StreamrootSDK.json" ~> 3.25.1
github "daltoniam/Starscream" ~> 3.1.1
github "getsentry/sentry-cocoa" ~> 4.4.0
github "apple/swift-protobuf" ~> 1.7.0
From 22.01
With Cocoapods
use_frameworks!
target '<Your Target Name> '
pod 'LumenMeshSDK', '~> 22.01.0'
end
With Carthage
binary "https://sdk.streamroot.io/ios/LumenMeshSDK.json"
Unique customer key setting
The unique identifier that you can find in the Account section of the Lumen Dashboard has been renamed from streamroot
to DeliveryClient
.
It must be added in the "Info.plist" as follows:
Before 22.01
<key>Streamroot</key>
<dict>
<key>Key</key>
<string>streamrootKey</string>
</dict>
From 22.01
<key>DeliveryClient</key>
<dict>
<key>Key</key>
<string>deliveryClientKey</string>
</dict>
Importing the SDK
The SDK name has been renamed from StreamrootSDK
to LumenOrchestratorSDK
.
Before 22.01
It also has been implemented in Objective-C, so to import it in a swift project a bridge-header is needed (see Apple documentation)
#import <StreamrootSDK/StreamrootSDK.h>
From 22.01
Just import the SDK
#import LumenMeshSDK
SDK Initialization
The SDK has now a new step for initialization.
It must be done from the App delegate application(_:didFinishLaunchingWithOptions:) function as follow:
Before 22.01
None
From 22.01
LMDeliveryClient.initializeApp()
Lumen Delivery Client instance build, start and stop.
The way to build the Lumen Delivery Client instances has changed in many ways from DNAClient.builder()
to LMDeliveryClientBuilder.clientBuilder()
and the start is now done in a separate method.
It is also not needed to pass the identifier at this stage anymore.
Before 22.01
Build and start of the DC instance are done at the same time as follow:
do {
dnaClient = try DNAClient.builder()
.dnaClientDelegate(self)
.latency(30)
.streamrootKey(<#streamrootKey#>)
.start(manifestUrl)
} catch let error {
print("\(error)")
}
And calling the stop()
method on the DC will stop the SDK.
dnaClient?.stop()
From 22.01
Build of the DC instance is done as follow:
deliveryClient = LMDeliveryClientBuilder.clientBuilder()
.playerInteractor(<#playerInteractor#>)
.contentId(<#string#>)
.meshProperty(<#string#>)
.build(<#manifestUrl#>)
And calling the start()
method on the DC will start the SDK.
deliveryClient?.start()
Then calling the stop()
method on the DC will stop the SDK.
deliveryClient?.stop()
Get new local URL
The method has been renamed from dnaClient?.manifestLocalURLPath
to deliveryClient?.localManifestURL
.
You must retrieve the new local URL as follows:
Before 22.01
guard let localPath = self.dnaClient?.manifestLocalURLPath,
let localUrl = URL(string: localPath) else {
print("Could not generate localPath, please check your network")
return
}
From 22.01
guard let deliveryUrl = deliveryClient?.localManifestURL else {
print("Local Url manifets could not be generated")
return
}
Integrate the Player interactor
A lots of changes have been made across the player interactor to always ensure best QOS and efficiency.
It now
Before 22.01
See QoSModule
From 22.01
import AVKit
import LumenMeshSDK
class PlayerInteractor: LMPlayerInteractorBase {
fileprivate var player: AVPlayer?
fileprivate var playbackState: LMPlaybackState
fileprivate var observer: Any?
override init() {
self.playbackState = .idle
super.init()
}
func linkPlayer(_ player: AVPlayer) {
self.player = player
guard let playerItem = player.currentItem else { return }
NotificationCenter.default.addObserver(self, selector: #selector(handlePlayedToEndFail),
name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
object: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(handlePlayToEndSucceded),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(handleAccesLogEntry),
name: NSNotification.Name.AVPlayerItemNewAccessLogEntry,
object: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(handleErrorLogEntry),
name: NSNotification.Name.AVPlayerItemNewErrorLogEntry,
object: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(handleItemPlayBackJumped),
name: AVPlayerItem.timeJumpedNotification,
object: playerItem)
NotificationCenter.default.addObserver(self, selector: #selector(handleItemPlayBackStall),
name: NSNotification.Name.AVPlayerItemPlaybackStalled,
object: playerItem)
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.new, context: nil)
observePlayback()
}
func unlink() {
if let observer = self.observer {
player?.removeTimeObserver(observer)
self.observer = nil
}
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime,
object: player?.currentItem)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player?.currentItem)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemNewAccessLogEntry,
object: player?.currentItem)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemNewErrorLogEntry,
object: player?.currentItem)
NotificationCenter.default.removeObserver(self, name: AVPlayerItem.timeJumpedNotification,
object: player?.currentItem)
NotificationCenter.default.removeObserver(self, name: NSNotification.Name.AVPlayerItemPlaybackStalled,
object: player?.currentItem)
player?.removeObserver(self, forKeyPath: "rate")
}
deinit {
unlink()
}
fileprivate func updateState(_ state: LMPlaybackState) {
if playbackState != state {
super.playerStateDidChange(state)
playbackState = state
}
}
public override func observeValue(forKeyPath keyPath: String?,
of _: Any?,
change _: [NSKeyValueChangeKey: Any]?,
context _: UnsafeMutableRawPointer?) {
if keyPath == "rate", player?.rate == 0.0 {
updateState(.paused)
}
}
fileprivate func observePlayback() {
if let observer = self.observer {
player?.removeTimeObserver(observer)
self.observer = nil
}
// Invoke callback every half second
let interval = CMTime(seconds: 1,
preferredTimescale: CMTimeScale(NSEC_PER_SEC))
// Queue on which to invoke the callback
let mainQueue = DispatchQueue.main
// Add time observer
observer = player?.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) { [weak self] _ in
guard let self = self else { return }
let playbackLikelyToKeepUp: Bool = self.player?.currentItem?.isPlaybackLikelyToKeepUp ?? false
if !playbackLikelyToKeepUp {
// rebuffering
self.updateState(.buffering)
} else {
// playing
let rate = self.player?.rate
if rate == 1.0, self.player?.error == nil {
self.updateState(.playing)
}
}
}
}
}
// MARK: - Handler
extension PlayerInteractor {
@objc private func handlePlayedToEndFail(_: Notification) {
super.playbackErrorOccurred()
}
@objc private func handlePlayToEndSucceded(_: Notification) {
updateState(.ended)
}
@objc private func handleAccesLogEntry(_: Notification) {
guard let playerEvents = player?.currentItem?.accessLog()?.events.first else {
return
}
// trackswitch
if playerEvents.switchBitrate > 0 {
super.trackSwitchOccurred()
}
// dropframe
if playerEvents.numberOfDroppedVideoFrames > 0 {
super.updateDroppedFrameCount(playerEvents.numberOfDroppedVideoFrames)
}
}
@objc private func handleErrorLogEntry(_: Notification) {
super.playbackErrorOccurred()
}
@objc private func handleItemPlayBackJumped(_: Notification) {
updateState(.seeking)
}
@objc private func handleItemPlayBackStall(_: Notification) {
updateState(.buffering)
}
}
// MARK: - PlayerInteractor
extension PlayerInteractor {
public override func playbackTime() -> Double {
if let player = self.player {
return CMTimeGetSeconds(player.currentTime())
}
return 0.0
}
public override func bufferHealth() -> Double {
if let playerItem = self.player?.currentItem {
let time = CMTimeGetSeconds(playerItem.currentTime())
let timeRanges = playerItem.loadedTimeRanges.filter { CMTimeGetSeconds($0.timeRangeValue.end) > time }
return timeRanges.map {
CMTimeGetSeconds($0.timeRangeValue.end)
}.reduce(0) { $0 + ($1 - time) }
}
return 0.0
}
public override func bufferTarget() -> Double {
if #available(iOS 10.0, tvOS 10.0, *) {
return self.player?.currentItem?.preferredForwardBufferDuration ?? 0
}
return 0.0
}
public override func setBufferTarget(_ target: Double) {
if #available(iOS 10.0, tvOS 10.0, *) {
self.player?.currentItem?.preferredForwardBufferDuration = target
}
}
public override func setEstimatedBandwidth(_ bps: NSNumber?) {
NSLog("[Lumen] setEstimatedBandwidth called with value \(bps != nil ? "\(bps!.uint64Value)" : "null")")
guard let bps = bps else { return }
self.player?.currentItem?.preferredPeakBitRate = bps.doubleValue
}
}
Debug tools (Stats View)
The Stats View has been completely redesigned and is part of a new library. More information at Debug tools for iOS/tvOS from SDK 22.01.
Before 22.01
A helper method is available to display various Mesh related stats on a specified UIView.
[self.dnaClient displayStatsOnView: self.contentOverlayView];
From 22.01
A helper method is available to display various Mesh related stats on a specified UIView.
self.deliveryClient.displayStatWiew(someView!)