Article

Presenting Content Proposals in 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. You add this functionality to your app using AVKit’s content proposals.

Create a Content Proposal

You create a content proposal using the AVContentProposal class. This type 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 a content proposal instance 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(.commonIdentifierDescription, value: "Episode 2 Description"),
        makeMetadataItem(.iTunesMetadataContentRating, value: "TV-14"),
        makeMetadataItem(.quickTimeMetadataGenre, value: "Comedy")
    ]

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

private func makeMetadataItem(_ identifier: AVMetadataIdentifier, 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 an interface to present this data to the user. You create this interface by subclassing the AVKit framework’s AVContentProposalViewController class. At runtime, the system passes your subclass 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, and should also include options for the user to accept or reject the proposal.

When the system presents your proposal, it displays it 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 system presents the proposal, the player’s view automatically animates to the specified frame.

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 dismissContentProposal(for:animated:completion:) method, indicating the user’s choice.

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

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

Make the Content Proposal Eligible to be Presented

To make your content proposal eligible for the system to present, set it as the nextContentProposal property value of the current AVPlayerItem. The following example shows how to configure this property value in a playback app that manages a queue of Video objects, which is a custom value type that models the data of an individual video in the queue. The example code 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 the system presents the content proposal, 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(_:shouldPresent:) 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, the player view controller can automatically handle its acceptance or rejection. However, if you need more control over the handling of these actions, implement the playerViewController(_:didAccept:) and playerViewController(_:didReject:) methods. For example, the following code 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 the new player item's next content proposal (if it exists)
    playerItem.nextContentProposal = makeContentProposal(for: currentVideo.nextVideo)
}

See Also

Proposing Content

class AVContentProposalViewController

A view controller used to create custom content proposal presentations.

class AVContentProposal

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