If you’ve ever worked with AVPlayer you probably encountered a few tricky concepts, namely KVO, CMTime and Media Groups. In this blog post we give some tips and explanations on how best to work with AVPlayer using SHMAVPlayerInterface.
The Challenge of AVPlayer
The typical workflow for AVPlayer requires creating the AVPlayerItem, setting it to AVPlayer and asking the player to start playing with player.play(). Then we usually implement KVO (key-value observing) methods to observe the player’s properties that are interesting to us. Later we might need look into media groups for available subtitles or perhaps seek playback to specific time via CMTime.
Especially when working with KVO, one needs to be careful (for more see useful blog post by Soroush Khanlou). KVO was not built with type safety in mind and unwise usage can lead to crashes. It’s often safer and easier to replace KVO with a different observer pattern. Thus we rarely encounter it in our Showmax app code. Except one place, where Apple requires it - the AVPlayer.
Creating a Friendly Interface
Our SHMAVPlayerInterface library is not a replacement or subclass for AVPlayer. Rather we’ve created a very light and decoupled wrapper around AVPlayer. This way, we’re not obstructing any AVPlayer features. Our helper code can be used seamlessly together with standard AVPlayer features.
The library is split into two parts:
- SHMAVPlayerInterface class with helper methods offering easier access to information about current state of AVPlayer and AVPlayerItem:
undefinedundefinedundefinedundefined - Reactive API for AVPlayer and AVPlayerItem replaces the need to implement KVO methods and provides the ability to create Observables for changes in:
undefinedundefinedundefinedundefinedundefinedundefined
Example Code
In the following example we show a simple use case for how to observe playback position in the played asset.
- we import SHMAVPlayerInterface so we have available helpers and our reactive API for AVPlayer.
- we subscribe to player’s observers to be notified about changes in playback position. After player.play() we will start receiving the notifications.
Note: It is important that you destroy DisposeBag used for observing certain AVPlayer instance before you destroy AVPlayer. In this example we do this with deinit. If this part is missing, a crash will occur.
import AVFoundation
import AVKit
import RxSwift
import SHMAVPlayerInterface
class PlayerManagerExample
{
let item: AVPlayerItem
let player: AVPlayer
let playerInterface: SHMAVPlayerInterface
var bag: DisposeBag
init(url: URL)
{
// Create AVPlayer
item = AVPlayerItem(asset: AVAsset(url: url))
player = AVPlayer(playerItem: item)
// Create interface
playerInterface = SHMAVPlayerInterface(player: player)
// Setup observers
// A) for changes in playback position
player.rx.playbackPosition(updateInterval: 2, updateQueue: nil)
.subscribe(
onNext: { position in
print(position)
}
)
.addDisposableTo(bag)
// B) for status and when status
player.rx.status(options: [.new])
.subscribe(
onNext: { [weak self] status in
guard status == .readyToPlay else { return }
print(self?.playerInterface.availableSubtitles.first?.languageCode)
}
)
.addDisposableTo(bag)
// Start playing
player.play()
}
deinit
{
bag = DisposeBag() // to cancel reactive subscriptions to AVPlayer
}
}
More examples on Github.
Where to get SHMAVPlayerInterface
SHMAVPlayerInterface is available on Cocoapods and Github.
Let us know how it works for you, or if you have any suggestions for improvements.
We welcome your feedback. Contact us at geeks@showmax.com.