Search code examples
swiftavplayerhttp-live-streamingid3m3u8

how to read id3 tags / other metadata from an HLS stream in swift / AVKIT


I am trying to gather some knowledge about how to read metadata from an HLS stream inside an iOS app. The following HLS stream has some ID3 tags which I want to read: HLS test stream

In the web inspector in Safari, I am able to see lots of data objects in the console, each one has metadata:

enter image description here

In the network tab of the web inspector, I can read the playlist file:

#EXTM3U
#EXT-X-VERSION:5
#EXT-X-MEDIA-SEQUENCE:89147
#EXT-X-TARGETDURATION:20
#EXT-X-PROGRAM-DATE-TIME:2019-09-25T11:35:23.401Z
#EXTINF:19.970,
05-20190925T113523Z.aac
#EXTINF:19.970,
05-20190925T113543Z.aac
#EXTINF:19.970,
05-20190925T113603Z.aac
#EXTINF:19.970,
05-20190925T113623Z.aac
#EXTINF:19.970,
05-20190925T113643Z.aac
#EXTINF:19.970,
05-20190925T113703Z.aac

So far I've implemented a class which uses an AVPlayer instance to play this stream. It is working properly.

I printed all sorts of properties from the AVPlayer and AVPlayerItem to the Xcode console. However, the only property I can interpret is AVPlayerItem.currentTime which gives me the value of EXT-X-PROGRAM-DATE-TIME from the playlist file. All other properties don't seem to have something to do with the information I see in the playlist and the id3 tags.

Is there any way I can read the metadata contained in each id3 tag? How can I read EXT-X-TARGETDURATION from the playlist?

I read about AVPlayerItemMetadataCollector, but I don't understand what it is supposed to do and if this would help me reading metadata in the HLS stream.


Solution

  • This is how I achieved it:

    import UIKit
    import AVKit
    import AVFoundation
    import MediaPlayer
    
    class ViewController: UIViewController{
    
        let player = AVPlayer()
        var playerItem: AVPlayerItem!
        let asset = AVAsset(url: URL(string: "https://db2.indexcom.com/bucket/ram/00/05/05.m3u8")!)
    
        override func viewDidLoad() {
            prepareToPlay()
            player.play()
        }
    
        func prepareToPlay() {
            playerItem = AVPlayerItem(asset: asset)
            playerItem.addObserver(self, forKeyPath: "timedMetadata", options: [], context: nil)
            player.replaceCurrentItem(with: playerItem)
            printTimeStamp()
        }
    
        func printTimeStamp() {
            print("▼⎺▼⎺▼⎺▼⎺▼⎺▼⎺▼⎺▼")
            print("PROGRAM-DATE-TIME: ")
            print(playerItem.currentDate() ?? "No timeStamp")
            print("▲_▲_▲_▲_▲_▲_▲_▲\n\n")
        }
    
        override func observeValue(forKeyPath: String?, of: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
            if forKeyPath != "timedMetadata" { return }
    
            printTimeStamp()
    
            let data: AVPlayerItem = of as! AVPlayerItem
    
            guard let timedMetadata = data.timedMetadata else { return }
    
            for item in timedMetadata {
                switch item.commonKey {
    
                case .commonKeyAlbumName?:
                    print("AlbumName: \(item.value!)")
                case .commonKeyArtist?:
                    print("Artist: \(item.value!)")
                case .commonKeyArtwork?:
                    print("Artwork: \(item.value!)")
                case .commonKeyAuthor?:
                    print("Author: \(item.value!)")
                case .commonKeyContributor?:
                    print("Contributor: \(item.value!)")
                case .commonKeyCopyrights?:
                    print("Copyrights: \(item.value!)")
                case .commonKeyCreationDate?:
                    print("CreationDate: \(item.value!)")
                case .commonKeyCreator?:
                    print("creator: \(item.value!)")
                case .commonKeyDescription?:
                    print("Description: \(item.value!)")
                case .commonKeyFormat?:
                    print("Format: \(item.value!)")
                case .commonKeyIdentifier?:
                    print("Identifier: \(item.value!)")
                case .commonKeyLanguage?:
                    print("Language: \(item.value!)")
                case .commonKeyMake?:
                    print("Make: \(item.value!)")
                case .commonKeyModel?:
                    print("Model: \(item.value!)")
                case .commonKeyPublisher?:
                    print("Publisher: \(item.value!)")
                case .commonKeyRelation?:
                    print("Relation: \(item.value!)")
                case .commonKeySoftware?:
                    print("Software: \(item.value!)")
                case .commonKeySubject?:
                    print("Subject: \(item.value!)")
                case .commonKeyTitle?:
                    print("Title: \(item.value!)")
                case .commonKeyType?:
                    print("Type: \(item.value!)")
    
                case .id3MetadataKeyAlbumTitle?:
                    print("id3MetadataKeyAlbumTitle: \(item.value!)")
    
                default:
                    print("other data: \(item.value!)")
                }
            }
        }
    }