HLS Timed Metadata with AVPlayer

David Cordero
2 min readFeb 1, 2021

This post was original published in dcordero.me, where you will always find the most updated, and free of spyware version of all my posts.

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 ***

--

--

David Cordero

iOS and tvOS developer at Zattoo. Passionate about coding and lifelong learning.