During the summer, we released V3 of our Player SDKs for Android and iOS, marking a significant milestone in our mission to deliver the best tools to achieve any simple or complex video workflow packaged into a best-in-class developer experience.
In this blog post, we will dive deeper into the more technical aspects of what V3 includes, going in-depth into the major new features like the Playlist API and other improvements made to each SDK.
How V3 came to be
V2 of our Mobile SDKs was designed around the playback of a single source loaded into the Player (e.g., a DASH, HLS, Smooth or Progressive stream). As all of the Player functionality was tied to a single source, it was impossible to load or prepare another source simultaneously. For the Player to playback a different source, the active source needed to be unloaded first, and only then could the new source begin loading.
V2 allows only single-source workflows.
The problem with this approach is that there is always some downtime involved when switching sources, during which the viewer will see a black screen until enough data from the new source has buffered. This downtime is a limiting factor when trying to provide an excellent user experience for viewers.
Downtime when transitioning from one source to another.
To address this issue, we developed and released V3 – a collection of major improvements to our core APIs as well as the addition of new ones.
Gapless playback
The first improvement I will go into is our Gapless Playback functionality. V3 represents a shift in our architecture whereby the playback Sources and the Player become more decoupled. This decoupling allows sources to have a life cycle and emit events. In addition, each source can be interacted with even when it is not actively playing, which enables the Player to manage multiple sources concurrently – playing the active source while preparing the other sources for playback.
With this upgrade, it is now possible to seamlessly playback multiple sources without a single frame of downtime because data from the following source can buffer while the previous one is still playing.
Achieving gapless playback in V3 by loading multiple sources into a player.
Managing multiple sources
With the promotion of Source
to a top-level entity comes a collection of powerful APIs that were previously only available via the
, acting on a single source. Now you can get duration data, query available audio and video qualities, set a specific subtitle track, and much more with each source without it actively playing back.Player
To enable working with multiple sources in the Player and help facilitate gapless playback, we introduced the Playlist API. It provides the means to dynamically add and remove sources, as well as a way to change the source that is playing.
Together, the new source and playlist APIs offer the power and flexibility to achieve a wide variety of new video experiences.
New Source and Playlist APIs are at the heart of V3.
What’s new for the Android SDK?
Embracing Kotlin
We’re continuously striving to offer a better developer experience for our users, so it’s only natural that we fully embrace Kotlin, which has become the standard for Android developers worldwide. Our team is a big fan of Kotlin and has been using it internally for a while now, which is why we were adamant about making the full power of Kotlin available for users of our Android SDK. For V3, we developed our APIs looking through a Kotlin lens and will continue to do so for future releases.
The result is a safe, modern, and ergonomic API surface for developers to utilize. In practice, this means that we use nullability information, default parameter values, sealed class hierarchies, extensions, reified functions, function types, and more awesome Kotlin features that improve the developer experience.
By embracing Kotlin, we implemented our new EventEmitter
, which improves how to manage event subscriptions, which leads us to our next section.
Improving the way of working with events
The latest mobile SDK comes with a complete rework on handling events. We got rid of over 70 lines of boilerplate code, helping make managing event subscriptions easier and usage more intuitive.
In V2, each event had a separate *Listener class associated with it which had to be used to subscribe to that event. This meant that you first had to find the event you are interested in, then find the associated *Listener
class and create an instance of that class to pass it into the EventHandler
(the Player
in this case).
V3 simplified this workflow. There are no more separate *Listener
classes and the event itself is all you need. If you want to subscribe to an event, you just need to pass that event and an action to the new EventEmitter
(the Player
or Source
, in this case). For Kotlin users, the action argument is just a standard Kotlin lambda. In contrast, for Java users, it’s a simple implementation of a new EventListener
interface, written as a Java lambda.
player.on(PlayerEvent.Ready::class) { println("Ready") } // alternatively player.on<PlayerEvent.Ready> { println(“Ready”) }
All events are now part of a sealed class hierarchy, making it easy to discover and autocomplete all available events in a particular situation. For Kotlin users, we additionally offer reified extension functions that make it even easier to manage event subscriptions by inferring the type of event that an action is associated with.
val onPlayerReady: (PlayerEvent.Ready) -> Unit = { println(“Ready”) } player.on(onPlayerReady) player.off(onPlayerReady)
There’s also a new API available that allows you to subscribe to just the next occurrence of an event. Subscribing an action will automatically unsubscribe the action after the first occurrence without requiring manual cleanup.
player.next<PlayerEvent.Ready> { println(“Ready”) }
Package restructuring & open-source API
Over the years, our public API became loosely spread around different packages, which hurts discoverability. To improve on this, we grouped APIs that belong together into common packages and put all of these common packages into a single api
root package, making it easier to discover API objects and how they interact with each other. At the same time, we converted most of them into abstract types or data classes, which should make it easier to mock the SDK in your tests.
With all of the public APIs in one package, we went a step further and now deliver the source code of this package with the SDK. As a result, it is now easy to click through the API within your development environment and see all of our public API code and documentation exactly how we wrote it. Additionally, making the public API open source means that developers have to consult the external documentation on our website less frequently.
New documentation in Kotlin
We’ve completely reworked our documentation to provide a much-improved learning experience on our website, including a landing page with general information, search functionality, and a cleaner design (with dark mode!). All of it is available in Kotlin and can be viewed here
New search functionality
Better discoverability using the new documentation
What’s new for the iOS SDK?
XCFramework
Starting with V3, we ship the SDK as an XCFramework. Apple introduced this new bundle format with Xcode 11 to support devices and simulators simultaneously. By shipping it as an XCFramework, we were able to add support for Apple’s first-party package manager, the Swift Package Manager.
Swift Package Manager support
We distribute the latest version of our SDK through Apple’s proprietary swift package manager (SPM). It is the easiest way to integrate our framework into any iOS/tvOS project, and we are excited to offer this convenience to our users.
Improving the way of creating a custom view
Creating a custom native UI (UIView
) on V2 was pretty tedious. It was necessary to subclass a specific type and use this as a base for the view. We simplified this in V3, and there is no longer the need to subclass any of our types to create a custom view. We achieved this by introducing a public API on the Player
where an AVPlayerLayer
or an AVPlayerViewController
can be registered.
Any custom UIView
subclass with an AVPlayerLayer
(or an AVPlayerViewController
) can now be used and registered with the player:
// Create a subclass of UIView class CustomView: UIView { init(player: Player, frame: CGRect) { super.init(frame: frame) // register the AVPlayerLayer of this view to the Player player.register(playerLayer) } var playerLayer: AVPlayerLayer { layer as! AVPlayerLayer } override class var layerClass: AnyClass { AVPlayerLayer.self } }
Register the view layer with the Player
by calling player.register(playerLayer:)
. Add the view to your view hierarchy and it will be complete.
Revamping the API for our offline Playback feature
V3 brings the iOS SDK closer to the Android SDK when it comes to using the offline playback feature. We deprecated the old API of the OfflineManager
and introduced an OfflineContentManager
that can you can use to manage the download process and the downloaded data for each SourceConfig
independently.
In addition to this newly designed API, we introduced the same listener-based event communication you are already familiar with from the Player
.
View our detailed tutorial about how to use our offline playback feature.
Wrapping it up
Our native Player SDKs for Android and iOS represent an evolution of our core APIs packed with new and exciting features. If you are currently using V2 of our SDKs or want to explore how it is to improve your current deployment, check out our Android or iOS Migration Guides. Additionally, if you want to test out our mobile and any other SDKs, you can sign up for a free 30-day trial, where you will have complete access to our getting started guides, such as these for Android and iOS.