Create a customized playlist experience (iOS)

Add a callback to modify, skip, and/or trigger tasks when a playlist item is about to load


🚧

When using this callback, be mindful of the following points:

  • Provide a response to the completion handler in all logical paths. Failing to do so can cause unpredictable behavior due to the blocking nature of the callback.
  • Use a weak reference to self in the callback block to avoid creating a retain cycle.

The setPlaylistItemCallback(_:) method enables you to play (unchanged or modified in real-time) or skip an upcoming playlist item. In addition, you can execute relevant asynchronous code, such as reporting to a server or updating the UI.

The method passes in the upcoming item, its position in the playlist, and the completion block for you to inform the player of your decision.


player.setPlaylistItemCallback { 
    (item: JWPlayerItem, 
     index: Int, 
     completion: (_ item: JWPlayerItem?) -> ()) 
    in
    // Modify, play, or skip logic here
}
ArgumentDescription
completion JWPlayerItemCompletionClosure that informs the player of your decision

Pass nil to skip or a JWPlayerItem to play (unchanged or modified).
index IntPosition of item in the playlist
item JWPlayerItemUpcoming item in the playlist


Example Scenario

Imagine developing a news streaming app. This app has a playlist that plays top news videos back-to-back. Viewers can also specify categories of news videos to skip.

You want to keep users engaged and informed, and you want to monetize the app through ads. You recognize that bombarding users with ads isn't user-friendly, especially when watching breaking news.


Solution

The setPlaylistItemCallback(_:) method can handle each condition:

  • Regular News: Play a preroll ad before the segment.
  • User Preferences: For a viewer who prefers not to watch entertainment news, skip all entertainment news videos.
  • Breaking News: Play the segment without ads for news segments bearing the "Breaking News:" prefix and display a breaking news banner to the user.
player.setPlaylistItemCallback { [weak self] (item: JWPlayerItem, index: Int, completion: @escaping (JWPlayerItem?) -> ()) in            
    // User Preference - in this snippet, backend uses `description` property for its categories
    if let category = item.description, self?.userSkipCategories.contains(category) == true {
        completion(nil) // skips the item, moves to the next up item
        return // shouldn't be necessary, but good for defensive programming/clarity of intent
    }

    // Breaking News
    if item.title.hasPrefix("Breaking News:") {
        self?.displayBreakingNewsBannerUIOnMainThread()
        completion(item) // plays item unchanged
        return
    } else {
        // Regular News - Add a preroll
        let adUrl = URL(string: "https://www.example.com/newPreroll.xml")!
        let preroll = try! JWAdBreakBuilder()
            .tags([adUrl])
            .offset(.preroll())
            .build()
        
        let modifiedItem = try! JWPlayerItemBuilder(from: item)
            .adSchedule(breaks: [preroll])
            .build()
        
        completion(modifiedItem)
        return
    }

    // Default Completion (Should ideally not be reached with the conditions above)
    completion(item)
}

🚧

In your production code, use robust error handling instead of using try!.