Managing Video Titles and Subtitles with AVFoundation

Extracting Video Title Metadata

To display the video title, the application must parse the metadata embedded within the media resource. This requires fetching the commonMetadata property from the AVAsset. When initializing the AVPlayerItem, the asset keys must be pre-loaded to ensure the data is ready when the player reaches the .readyToPlay state.

Defining the Delegate Protocol

First, define the necessary methods in the player control delegate to handle UI updates for titles and subtitle lists.


protocol VideoControlDelegate: AnyObject {
    var playerDelegate: VideoPlayerDelegate? { get set }
    
    func beginPlayback()
    func updatePlaybackProgress(current: TimeInterval, total: TimeInterval)
    func configureVideoTitle(_ title: String)
    func configureSubtitleOptions(_ options: [String])
    func handlePlaybackCompletion()
}

In the view controller or control view, implement the title configuration method to update the label text.


func configureVideoTitle(_ title: String) {
    videoTitleLabel.text = title
}

Preparing the Asset with Metadata Keys

When preparing the player, specify commonMetadata in the array of asset keys to load asynchronously.


private func initializePlayer() {
    let requiredAssetKeys = [
        "tracks",
        "duration",
        "commonMetadata"
    ]
    
    guard let mediaAsset = mediaAsset else { return }
    
    playerItem = AVPlayerItem(asset: mediaAsset, 
                               automaticallyLoadedAssetKeys: requiredAssetKeys)
    
    guard let item = playerItem else { return }
    
    player = AVPlayer(playerItem: item)
    playerLayer = AVPlayerLayer(player: player)
    
    controlDelegate = overlayView?.controls
    controlDelegate?.playerDelegate = self
    
    item.addObserver(self, forKeyPath: "status", 
                     options: [.new, .initial], 
                     context: &playerItemContext)
}

Observing Status and Retrieving Title

Once the player item status changes to .readyToPlay, extract the title string from the metadata and pass it to the delegate.


override func observeValue(forKeyPath keyPath: String?, 
                          of object: Any?, 
                          change: [NSKeyValueChangeKey : Any]?, 
                          context: UnsafeMutableRawPointer?) {
    if context == &playerItemContext {
        guard let item = playerItem else { return }
        guard let player = player else { return }
        
        if item.status == .readyToPlay {
            item.removeObserver(self, forKeyPath: "status")
            player.play()
            
            let totalDuration = item.duration
            let seconds = CMTimeGetSeconds(totalDuration)
            
            controlDelegate?.beginPlayback()
            controlDelegate?.updatePlaybackProgress(current: 0.0, total: seconds)
            
            let extractedTitle = retrieveTitleFromAsset()
            controlDelegate?.configureVideoTitle(extractedTitle)
            
            setupTimeObserver()
        }
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

private func retrieveTitleFromAsset() -> String {
    guard let asset = mediaAsset else { return "" }
    
    let titleKey = AVMetadataCommonKeyTitle
    let titleItems = AVMetadataItem.metadataItems(from: asset.commonMetadata, 
                                                  withKey: titleKey, 
                                                  keySpace: AVMetadataKeySpaceCommon)
    
    if let titleItem = titleItems.first, 
       let titleString = titleItem.stringValue {
        return titleString
    }
    return ""
}

Implementing Subtitle Selection

AVFoundation provides robust support for closed captions and subtitles using AVMediaSelectionGroup and AVMediaSelectionOption. The AVPlayerLayer renders legible media automatically if the correct option is selected within the AVPlayerItem.

Loading Media Selection Options

Modify the asset loading phase to include availableMediaCharacteristicsWithMediaSelectionOptions. This allows the inspection of audio, video, and legible (subtitle) tracks.


private func initializePlayer() {
    let requiredAssetKeys = [
        "tracks",
        "duration",
        "commonMetadata",
        "availableMediaCharacteristicsWithMediaSelectionOptions"
    ]
    
    guard let mediaAsset = mediaAsset else { return }
    
    playerItem = AVPlayerItem(asset: mediaAsset, 
                               automaticallyLoadedAssetKeys: requiredAssetKeys)
    // ... (player setup code)
}

In the observation handler, call a method to fetch the available subtitle options.


// Inside observeValue... readyToPlay block
let subtitleList = fetchAvailableSubtitles()
controlDelegate?.configureSubtitleOptions(subtitleList)

private func fetchAvailableSubtitles() -> [String] {
    var subtitleTitles = [String]()
    guard let asset = mediaAsset else { return subtitleTitles }
    
    let legibleCharacteristic = AVMediaCharacteristic.legible
    
    asset.loadMediaSelectionGroup(for: legibleCharacteristic) { [weak self] group, error in
        guard let self = self, let selectionGroup = group else { return }
        
        let titles = selectionGroup.options.map { $0.displayName }
        self.controlDelegate?.configureSubtitleOptions(titles)
    }
    
    return subtitleTitles 
}

User Interface for Subtitle Selection

Implement the delegate method in the control view to store the options and create a button interaction that presents the list.


func configureSubtitleOptions(_ options: [String]) {
    availableSubtitles = options
}

@objc func showSubtitleSelectionMenu() {
    guard let subtitles = availableSubtitles, !subtitles.isEmpty else { return }
    guard let presenter = window?.rootViewController else { return }
    
    let sheet = UIAlertController(title: "Select Subtitle", 
                                  message: nil, 
                                  preferredStyle: .actionSheet)
    
    for subtitleName in subtitles {
        let action = UIAlertAction(title: subtitleName, style: .default) { [weak self] _ in
            self?.playerDelegate?.chooseSubtitle(subtitleName)
        }
        sheet.addAction(action)
    }
    
    let disableAction = UIAlertAction(title: "Off", style: .destructive) { [weak self] _ in
        self?.playerDelegate?.chooseSubtitle("")
    }
    sheet.addAction(disableAction)
    
    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
    sheet.addAction(cancelAction)
    
    presenter.present(sheet, animated: true)
}

Applying the Subtitle Selection

When the user selects an option, the player controller must load the media selection group and apply the chosen option to the AVPlayerItem.


func chooseSubtitle(_ subtitleName: String) {
    guard let asset = mediaAsset else { return }
    guard let item = playerItem else { return }
    
    let characteristic = AVMediaCharacteristic.legible
    
    asset.loadMediaSelectionGroup(for: characteristic) { group, error in
        guard let group = group else { return }
        
        if subtitleName.isEmpty {
            // Turn off subtitles
            item.select(nil, in: group)
            return
        }
        
        // Find matching option
        if let matchedOption = group.options.first(where: { $0.displayName == subtitleName }) {
            item.select(matchedOption, in: group)
        }
    }
}

Tags: iOS AVFoundation Swift VideoPlayback MediaMetadata

Posted on Fri, 19 Jun 2026 17:28:01 +0000 by irbrian