Article

Presenting Content Proposals (tvOS)

Display a preview of an upcoming media item at the conclusion of the currently playing media item.

Overview

Media apps presenting serialized content, such as a TV show, often display a preview of the next episode in the series when you finish watching the current one. The user interface for this preview usually contains artwork and information about the proposed content. It also includes options for the user to either watch the next episode or return to the main menu. Implementing this feature before tvOS 10 was often challenging, but now it’s easy to add this capability to your app using AVKit content proposals.

Create the Content Proposal

You create a content proposal using the AVContentProposal class. This class models the data about the proposed content, such as its title, preview image, metadata, and content URL, and the time at which to present the proposal. You create and configure an instance of AVContentProposal as shown in the following code:

func makeProposal() -> AVContentProposal {

    // Present 10 seconds prior to the end of current presentation
    let time = currentAsset.duration - CMTime(value: 10, timescale: 1)
    let title = "My Show: Episode 2"
    let image = UIImage(named: "ms_ep2")!
    let proposal = AVContentProposal(contentTimeForTransition: time, title: title, previewImage: image)

    // Set custom metadata
    proposal.metadata = [
        makeMetadataItem(identifier: AVMetadataCommonIdentifierDescription, value: "Episode 2 Description"),
        makeMetadataItem(identifier: AVMetadataIdentifieriTunesMetadataContentRating, value: "TV-14"),
        makeMetadataItem(identifier: AVMetadataIdentifierQuickTimeMetadataGenre, value: "Comedy")
    ]

    // Set content URL
    proposal.url = // The upcoming asset's URL
    return proposal
}

private func makeMetadataItem(_ identifier: String, value: Any) -> AVMetadataItem {
    let item = AVMutableMetadataItem()
    item.identifier = identifier
    item.value = value as? NSCopying & NSObjectProtocol
    item.extendedLanguageTag = "und"
    return item.copy() as! AVMetadataItem
}

Create the Content Proposal's User Interface

In addition to defining your content proposal’s data, you also need to create a user interface to present this data to the user. You create this interface by subclassing the AVKit framework’s AVContentProposalViewController class. At runtime, your subclass instance is passed a reference to the current AVContentProposal, providing you the data to present. Your user interface should provide visual and descriptive information about the proposed content. It should also include options for the user to accept or reject the proposal.

When your proposal is presented, it's displayed over the currently playing full-screen video. You may want to scale this video to a smaller size so you can make more room to display the details of the proposed content. To do this, you override the view controller’s preferredPlayerViewFrame property and return the desired video frame.

override var preferredPlayerViewFrame: CGRect {
    guard let frame = playerViewController?.view.frame else { return CGRect.zero }
    // Present the current video in a 960x540 window centered at the top of the window
    return CGRect(x: frame.midX / 2.0, y: 0, width: 960, height: 540)
}

When the content proposal is presented, the player’s view is automatically animated to the specified CGRect.

Add Controls to the Content Proposal

Your presented user interface should also provide controls so the user can accept or reject the proposal. The event handlers for these actions should call the controller’s dismissContentProposalForAction:animated:completion: method, indicating the user’s choice.

// Handle acceptance
@IBAction func acceptContentProposal(_ sender: AnyObject) {
    dismissContentProposal(for: .accept, animated: true, completion: nil)
}

// Handle rejection
@IBAction func rejectContentProposal(_ sender: AnyObject){
    dismissContentProposal(for: .reject, animated: true, completion: nil)
}

Make the Content Proposal Eligible to be Presented

To make your content proposal eligible to be presented, set it as nextContentProposal of the current AVPlayerItem. The following example shows how this could be set up in a playback app that manages a queue of Video objects—a custom value type, modeling the data of an individual video in the queue. The example creates the required playback objects, creates a new AVContentProposal for the next video in the queue, and sets the video as the player item’s nextContentProposal.

func prepareToPlay() {
    // Associate the AVPlayer with the AVPlayerViewController
    playerViewController.player = player
    playerViewController.delegate = self

    // Create a new AVPlayerItem with the current video's URL
    let playerItem = AVPlayerItem(url: currentVideo.url)
    player.replaceCurrentItem(with: playerItem)

    // Create an AVContentProposal for the next video (if it exists)
    playerItem.nextContentProposal = makeContentProposal(for: currentVideo.nextVideo)
    player.play()
}
 
func makeContentProposal(for video: Video?) -> AVContentProposal? {
    guard let video = video else { return nil }
    guard let currentAsset = player.currentItem?.asset else { return nil } 

    // Start the proposal within presentationInterval of the asset's duration
    let time = currentAsset.duration - video.presentationInterval
    let title = video.title
    let image = video.image

    // Create a new content proposal with the time, title, and image
    let contentProposal = AVContentProposal(contentTimeForTransition: time, title: title, previewImage: image)

    // Set the content URL for the proposal
    contentProposal.url = video.url
    // Automatically accept the proposal 1 second from playback end time
    contentProposal.automaticAcceptanceInterval = -1.0
    return contentProposal
}

Present the Content Proposal

With the content proposal set as the player item’s nextContentProposal, the next step is to implement the methods of the AVPlayerViewControllerDelegate protocol. You use these methods to define how a content proposal is presented, as well as to handle the acceptance or rejection of the proposed content.

To present your custom view controller in response to a request to present the next content proposal, implement the playerViewController:shouldPresentContentProposal: method. In this method, you set an instance of your custom AVContentProposalViewController as the player view controller’s contentProposalViewController property.

func playerViewController(_ playerViewController: AVPlayerViewController, shouldPresent proposal: AVContentProposal) -> Bool {
    // Set the presentation to use on the player view controller for this content proposal
    playerViewController.contentProposalViewController = NextVideoProposalViewController()
    return true
}

If the presented AVContentProposal provides a valid content URL, AVPlayerViewController can automatically handle its acceptance or rejection. However, if you need more control over the handling of these actions, you can implement the playerViewController:didAcceptContentProposal: and playerViewController:didRejectContentProposal: methods. For instance, the following example implements the playerViewController(_:didAccept:) method to play the proposed video and create a new content proposal for the next video in the queue.

func playerViewController(_ playerViewController: AVPlayerViewController, didAccept proposal: AVContentProposal) {
    guard let player = playerViewController.player, let url = proposal.url else { return }
    guard let video = currentVideo.nextVideo else { return }
    currentVideo = video

    // Create a new player item using the content proposal's URL
    let playerItem = AVPlayerItem(url: url)
    player.replaceCurrentItem(with: playerItem)
    player.play()

    // Prepare new player item's next content proposal (if it exists)
    playerItem.nextContentProposal = makeContentProposal(for: currentVideo.nextVideo)
}

See Also

Up Next Content

AVContentProposalViewController

A view controller used to create custom content proposal presentations.

AVContentProposal

An object that describes the content proposed to follow the current item.