David Cordero
HLS Timed Metadata with AVPlayer
Published on 01 Feb 2021
As documented by Apple, HTTP Live Streaming (HLS) supports the inclusion of timed metadata in ID3 format.
In order to get this in-stream timed metadata from the client, we can make use of AVPlayerItemMetadataOutputPushDelegate.
Please notice that even though getting access to timed metadata used to be straightforward by observing the property timedMetadata of AVPlayerItem. Recently, with the release of iOS 13.0, Apple marked this property as deprecated.
You can find here below an example of a simple player catching and logging timed metadata to the console using AVPlayerItemMetadataOutputPushDelegate.
The example uses one of the example streams from Apple which contains timed metadata (time code every 5 seconds).
And here is the code
import UIKit
import AVFoundation
class PlaygroundPlayerViewController: UIViewController, AVPlayerItemMetadataOutputPushDelegate {
// MARK: - UIViewController
override func viewDidLoad() {
super.viewDidLoad()
setUpPlayerLayer()
let stream = URL(string: "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8")!
play(url: stream)
}
// MARK: - AVPlayerItemMetadataOutputPushDelegate
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
if let item = groups.first?.items.first
{
item.value(forKeyPath: #keyPath(AVMetadataItem.value))
let metadataValue = (item.value(forKeyPath: #keyPath(AVMetadataItem.value))!)
print("Metadata value: \n \(metadataValue)")
} else {
print("MetaData Error")
}
}
// MARK: - Private
private var playerLayer: AVPlayerLayer!
private var player: AVPlayer!
private var playerItem: AVPlayerItem!
private func play(url: URL?) {
guard let url = url else { return }
let asset = AVAsset(url: url)
playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
let metadataOutput = AVPlayerItemMetadataOutput(identifiers: nil)
metadataOutput.setDelegate(self, queue: DispatchQueue.main)
playerItem.add(metadataOutput)
playerLayer.player = player
player.play()
}
private func setUpPlayerLayer() {
playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = view.bounds
view.layer.addSublayer(playerLayer)
}
}
The output in the console looks like this:
Metadata value:
*** THIS IS Timed MetaData @ -- 00:00:00.0 ***
Metadata value:
*** THIS IS Timed MetaData @ -- 00:00:05.0 ***
Metadata value:
*** THIS IS Timed MetaData @ -- 00:00:10.0 ***
Metadata value:
*** THIS IS Timed MetaData @ -- 00:00:15.0 ***
Metadata value:
*** THIS IS Timed MetaData @ -- 00:00:20.0 ***